mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-14 01:27:34 -05:00
Compare commits
2 Commits
2628-remov
...
signal-hou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66c5727145 | ||
|
|
a53f6e9d99 |
54
build.gradle
54
build.gradle
@@ -109,7 +109,6 @@ dependencies {
|
|||||||
implementation 'ch.qos.logback:logback-classic:1.6.6'
|
implementation 'ch.qos.logback:logback-classic:1.6.6'
|
||||||
implementation 'ch.qos.logback:logback-core:1.6.6'
|
implementation 'ch.qos.logback:logback-core:1.6.6'
|
||||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.23.0'
|
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.23.0'
|
||||||
implementation 'com.fazecast:jSerialComm:2.11.4'
|
|
||||||
implementation 'com.formdev:flatlaf:3.8'
|
implementation 'com.formdev:flatlaf:3.8'
|
||||||
implementation 'com.formdev:flatlaf-intellij-themes:3.8' //Not imported - Theme.java loads these themes by class name
|
implementation 'com.formdev:flatlaf-intellij-themes:3.8' //Not imported - Theme.java loads these themes by class name
|
||||||
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
|
implementation 'com.github.jiconfont:jiconfont-font_awesome:4.7.0.1'
|
||||||
@@ -133,6 +132,7 @@ dependencies {
|
|||||||
implementation 'io.github.dsheirer:jlimesdr:25.1.0.2' //LimeSDR tuners - GitHub release, see settings.gradle
|
implementation 'io.github.dsheirer:jlimesdr:25.1.0.2' //LimeSDR tuners - GitHub release, see settings.gradle
|
||||||
implementation 'io.github.dsheirer:jrtl:1.4.0.3' //RTL-SDR tuners - GitHub release, see settings.gradle
|
implementation 'io.github.dsheirer:jrtl:1.4.0.3' //RTL-SDR tuners - GitHub release, see settings.gradle
|
||||||
implementation 'io.github.dsheirer:jsdrplay:3.15.3' //SDRplay RSP tuners - GitHub release, see settings.gradle
|
implementation 'io.github.dsheirer:jsdrplay:3.15.3' //SDRplay RSP tuners - GitHub release, see settings.gradle
|
||||||
|
implementation 'io.github.dsheirer:jsignalhound:2026.10.05' //Signal Hound tuners - GitHub release, see settings.gradle
|
||||||
implementation 'io.github.dsheirer:radio-reference-api:18.0.0'
|
implementation 'io.github.dsheirer:radio-reference-api:18.0.0'
|
||||||
implementation 'org.apache.commons:commons-compress:1.28.0' //Not used directly - overrides the vulnerable 1.21 version from jarchivelib
|
implementation 'org.apache.commons:commons-compress:1.28.0' //Not used directly - overrides the vulnerable 1.21 version from jarchivelib
|
||||||
implementation 'org.apache.commons:commons-lang3:3.21.0'
|
implementation 'org.apache.commons:commons-lang3:3.21.0'
|
||||||
@@ -278,38 +278,17 @@ def configure(RuntimeZipTask rt, List<String> jvmArgs) {
|
|||||||
rt.extension.imageZip.set(file(layout.buildDirectory.dir('image').get().file('sdr-trunk.zip')))
|
rt.extension.imageZip.set(file(layout.buildDirectory.dir('image').get().file('sdr-trunk.zip')))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Native library folders to keep in each runtime target image, keyed by the target name prefix. The tuner libraries
|
|
||||||
* (jRTL, jAirspy, etc.) package their native libraries under '<library>/native/<os>-<arch>/' and jSerialComm packages
|
|
||||||
* them under '<OS>/<arch>/'. All other operating system and architecture folders are removed from the target image.
|
|
||||||
*/
|
|
||||||
def nativeFolders = [
|
|
||||||
'linux-aarch64' : [tuner: 'linux-aarch64', serial: 'Linux/armv8_64'],
|
|
||||||
'linux-x86_64' : [tuner: 'linux-x86_64', serial: 'Linux/x86_64'],
|
|
||||||
'osx-aarch64' : [tuner: 'macos-aarch64', serial: 'OSX/aarch64'],
|
|
||||||
'osx-x86_64' : [tuner: 'macos-x86_64', serial: 'OSX/x86_64'],
|
|
||||||
'windows-aarch64': [tuner: 'windows-aarch64', serial: 'Windows/aarch64'],
|
|
||||||
'windows-x86_64' : [tuner: 'windows-x86_64', serial: 'Windows/x86_64']
|
|
||||||
]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Top level folders that jSerialComm uses for its per-OS native libraries.
|
|
||||||
*/
|
|
||||||
def serialOsFolders = ['Android', 'FreeBSD', 'Linux', 'OpenBSD', 'OSX', 'Solaris', 'Windows'] as Set
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rewrites the jar file without the native library folders for other operating systems and architectures. Fails the
|
* Rewrites the jar file without the native library folders for other operating systems and architectures. Fails the
|
||||||
* build if a jar that contains native libraries doesn't contain the folder for the target.
|
* build if a jar that contains native libraries doesn't contain the folder for the target.
|
||||||
* @param jar file to rewrite
|
* @param jar file to rewrite
|
||||||
* @param keep native folders for the target (tuner and serial)
|
* @param keep native folder for the target
|
||||||
* @param serialOsFolders jSerialComm top level OS folders
|
|
||||||
* @return number of bytes removed from the jar
|
* @return number of bytes removed from the jar
|
||||||
*/
|
*/
|
||||||
long stripJar(File jar, Map<String,String> keep, Set<String> serialOsFolders) {
|
long stripJar(File jar, String keep) {
|
||||||
def tunerPattern = ~/^[^\/]+\/native\/([^\/]+)\/.*/
|
def tunerPattern = ~/^[^\/]+\/native\/([^\/]+)\/.*/
|
||||||
def serialPrefix = keep.serial + '/'
|
|
||||||
def removed = [] as Set
|
def removed = [] as Set
|
||||||
def hadTuner = false, keptTuner = false, hadSerial = false, keptSerial = false
|
def hadTuner = false, keptTuner = false
|
||||||
File temp = new File(jar.parentFile, jar.name + '.tmp')
|
File temp = new File(jar.parentFile, jar.name + '.tmp')
|
||||||
|
|
||||||
new java.util.zip.ZipFile(jar).withCloseable { zip ->
|
new java.util.zip.ZipFile(jar).withCloseable { zip ->
|
||||||
@@ -317,23 +296,15 @@ long stripJar(File jar, Map<String,String> keep, Set<String> serialOsFolders) {
|
|||||||
|
|
||||||
entries.each { e ->
|
entries.each { e ->
|
||||||
def tuner = tunerPattern.matcher(e.name)
|
def tuner = tunerPattern.matcher(e.name)
|
||||||
def top = e.name.contains('/') ? e.name.substring(0, e.name.indexOf('/')) : null
|
|
||||||
|
|
||||||
if(tuner.matches()) {
|
if(tuner.matches()) {
|
||||||
hadTuner = true
|
hadTuner = true
|
||||||
if(tuner.group(1) == keep.tuner) { keptTuner = true } else { removed << e.name }
|
if(tuner.group(1) == keep) { keptTuner = true } else { removed << e.name }
|
||||||
}
|
|
||||||
else if(top in serialOsFolders) {
|
|
||||||
hadSerial = true
|
|
||||||
if(e.name.startsWith(serialPrefix)) { keptSerial = true } else { removed << e.name }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(hadTuner && !keptTuner) {
|
if(hadTuner && !keptTuner) {
|
||||||
throw new GradleException("Native strip: ${jar.name} has no native/${keep.tuner} folder")
|
throw new GradleException("Native strip: ${jar.name} has no native/${keep} folder")
|
||||||
}
|
|
||||||
if(hadSerial && !keptSerial) {
|
|
||||||
throw new GradleException("Native strip: ${jar.name} has no ${keep.serial} folder")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!removed.isEmpty()) {
|
if(!removed.isEmpty()) {
|
||||||
@@ -362,21 +333,18 @@ long stripJar(File jar, Map<String,String> keep, Set<String> serialOsFolders) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the native libraries for other operating systems and architectures from the jars in each runtime target
|
* Removes the native libraries for other operating systems and architectures from the jars in each runtime target
|
||||||
* image, before the runtimeZip tasks package the images.
|
* image, before the runtimeZip tasks package the images. The tuner libraries (jRTL, jAirspy, etc.) package their
|
||||||
|
* native libraries under '<library>/native/<os>-<arch>/', where the folder name matches the runtime target name
|
||||||
|
* '<os>-<arch>-v<version>' without the version suffix, except that the libraries use 'macos' where the target uses 'osx'.
|
||||||
*/
|
*/
|
||||||
tasks.named('runtime') {rt ->
|
tasks.named('runtime') {rt ->
|
||||||
rt.doLast {
|
rt.doLast {
|
||||||
rt.targetPlatforms.get().keySet().each { target ->
|
rt.targetPlatforms.get().keySet().each { target ->
|
||||||
def key = nativeFolders.keySet().find { target.startsWith(it + '-') }
|
def nativeFolder = (target - "-v${version}").replaceFirst(/^osx-/, 'macos-')
|
||||||
|
|
||||||
if(key == null) {
|
|
||||||
throw new GradleException("Native strip: no native folder mapping for runtime target [${target}]")
|
|
||||||
}
|
|
||||||
|
|
||||||
def lib = new File(rt.imageDir.asFile, "${project.name}-${target}/lib")
|
def lib = new File(rt.imageDir.asFile, "${project.name}-${target}/lib")
|
||||||
long total = 0
|
long total = 0
|
||||||
lib.listFiles({ File f -> f.name.endsWith('.jar') } as FileFilter).each { jar ->
|
lib.listFiles({ File f -> f.name.endsWith('.jar') } as FileFilter).each { jar ->
|
||||||
total += stripJar(jar, nativeFolders[key], serialOsFolders)
|
total += stripJar(jar, nativeFolder)
|
||||||
}
|
}
|
||||||
logger.lifecycle("Native strip: removed ${String.format('%.1f', total / 1048576)} MB of other " +
|
logger.lifecycle("Native strip: removed ${String.format('%.1f', total / 1048576)} MB of other " +
|
||||||
"platform native libraries from [${target}]")
|
"platform native libraries from [${target}]")
|
||||||
|
|||||||
1023
sdr-trunk.ipr
1023
sdr-trunk.ipr
File diff suppressed because it is too large
Load Diff
@@ -75,6 +75,15 @@ dependencyResolutionManagement {
|
|||||||
content { includeModule 'io.github.dsheirer', 'jsdrplay' }
|
content { includeModule 'io.github.dsheirer', 'jsdrplay' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//jSignalHound is published as a GitHub release. The jar does not contain the Signal Hound API native libraries,
|
||||||
|
//which users install separately from the Signal Hound SDK
|
||||||
|
ivy {
|
||||||
|
url = 'https://github.com/DSheirer/jSignalHound/releases/download'
|
||||||
|
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
|
||||||
|
metadataSources { artifact() }
|
||||||
|
content { includeModule 'io.github.dsheirer', 'jsignalhound' }
|
||||||
|
}
|
||||||
|
|
||||||
//jBladeRF is published as a GitHub release, with the native libraries for all platforms in the jar
|
//jBladeRF is published as a GitHub release, with the native libraries for all platforms in the jar
|
||||||
ivy {
|
ivy {
|
||||||
url = 'https://github.com/DSheirer/jBladeRF/releases/download'
|
url = 'https://github.com/DSheirer/jBladeRF/releases/download'
|
||||||
|
|||||||
@@ -1,483 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv;
|
|
||||||
|
|
||||||
import com.fazecast.jSerialComm.SerialPort;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessage;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessageFactory;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.VocoderRate;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.request.AmbeRequest;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.request.DecodeSpeechRequest;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.request.EncodeSpeechRequest;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.request.ResetRequest;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.request.SetVocoderParametersRequest;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.request.SetVocoderRequest;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.DecodeSpeechResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.ReadyResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderParameterResponse;
|
|
||||||
import io.github.dsheirer.sample.Listener;
|
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Northwest Digital Radio (NWDR) ThumbDv dongle.
|
|
||||||
*
|
|
||||||
* Note: linux users must allow access to the serial port:
|
|
||||||
*
|
|
||||||
* sudo usermod -a -G uucp username
|
|
||||||
* sudo usermod -a -G dialout username
|
|
||||||
* sudo usermod -a -G lock username
|
|
||||||
* sudo usermod -a -G tty username
|
|
||||||
*/
|
|
||||||
public class ThumbDv implements AutoCloseable
|
|
||||||
{
|
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(ThumbDv.class);
|
|
||||||
private static final String PORT_DESCRIPTION = "USB-to-Serial Port (ftdi_sio)";
|
|
||||||
private static final String PORT_DESCRIPTION_FRAGMENT = "USB Serial Port";
|
|
||||||
private static final int BAUD_RATE = 460800;
|
|
||||||
private static final byte PACKET_START = (byte) 0x61;
|
|
||||||
|
|
||||||
public enum AudioProtocol
|
|
||||||
{
|
|
||||||
DMR,
|
|
||||||
DSTAR,
|
|
||||||
NXDN,
|
|
||||||
P25_PHASE2,
|
|
||||||
}
|
|
||||||
|
|
||||||
private SerialPort mSerialPort;
|
|
||||||
private ScheduledFuture mSerialPortReaderHandle;
|
|
||||||
private ScheduledFuture mAudioDecodeProcessorHandle;
|
|
||||||
private LinkedTransferQueue<DecodeSpeechRequest> mDecodeSpeechRequests = new LinkedTransferQueue<>();
|
|
||||||
private AudioProtocol mAudioProtocol;
|
|
||||||
private Listener<float[]> mAudioBufferListener;
|
|
||||||
private boolean mStarted;
|
|
||||||
|
|
||||||
public ThumbDv(AudioProtocol audioProtocol, Listener<float[]> listener)
|
|
||||||
{
|
|
||||||
mAudioProtocol = audioProtocol;
|
|
||||||
mAudioBufferListener = 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 && mAudioBufferListener != null)
|
|
||||||
{
|
|
||||||
float[] samples = ((DecodeSpeechResponse)message).getSamples();
|
|
||||||
mAudioBufferListener.receive(samples);
|
|
||||||
}
|
|
||||||
else if(message instanceof ReadyResponse && mAudioDecodeProcessorHandle == null)
|
|
||||||
{
|
|
||||||
if(mAudioDecodeProcessorHandle == null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
switch(mAudioProtocol)
|
|
||||||
{
|
|
||||||
case DSTAR:
|
|
||||||
send(new SetVocoderRequest(VocoderRate.RATE_33));
|
|
||||||
send(new SetVocoderParametersRequest(0x0130, 0x0763, 0x4000, 0x0000, 0x0000, 0x0048));
|
|
||||||
break;
|
|
||||||
case DMR:
|
|
||||||
case NXDN:
|
|
||||||
case P25_PHASE2:
|
|
||||||
send(new SetVocoderRequest(VocoderRate.RATE_33));
|
|
||||||
send(new SetVocoderParametersRequest(0x0431, 0x0754, 0x2400, 0x0000, 0x0000, 0x6F48));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unrecognized audio protocol:" + mAudioProtocol);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(IOException ioe)
|
|
||||||
{
|
|
||||||
mLog.error("Error setting audio protocol vocoder parameters");
|
|
||||||
}
|
|
||||||
|
|
||||||
mLog.info("ThumbDv Reset Complete");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mLog.info("ThumbDv Reset Detected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(message instanceof SetVocoderParameterResponse)
|
|
||||||
{
|
|
||||||
if(((SetVocoderParameterResponse)message).isSuccessful())
|
|
||||||
{
|
|
||||||
mLog.info("Audio vocoder parameters configured for " + mAudioProtocol);
|
|
||||||
//Start the audio frame decode processor
|
|
||||||
mAudioDecodeProcessorHandle = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioDecodeProcessor(), 0,
|
|
||||||
10, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(message != null)
|
|
||||||
{
|
|
||||||
mLog.debug("RECEIVED:" + message.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs the current settings of the serial port. Note: you must invoke start() before this method so that
|
|
||||||
* the serial port can be discovered and opened.
|
|
||||||
*/
|
|
||||||
public void logSerialPort()
|
|
||||||
{
|
|
||||||
if(mSerialPort != null)
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append("\nPort:\t\t\t").append(mSerialPort.getSystemPortName()).append("\n");
|
|
||||||
sb.append("Name:\t\t\t").append(mSerialPort.getDescriptivePortName()).append("\n");
|
|
||||||
sb.append("Baud Rate:\t\t").append(mSerialPort.getBaudRate()).append("\n");
|
|
||||||
sb.append("Data Bits:\t\t").append(mSerialPort.getNumDataBits()).append("\n");
|
|
||||||
sb.append("Parity Bits:\t").append(mSerialPort.getParity()).append("\n");
|
|
||||||
sb.append("Stop Bits:\t\t").append(mSerialPort.getNumStopBits()).append("\n");
|
|
||||||
sb.append("Flow Control:\t").append(mSerialPort.getFlowControlSettings()).append("\n");
|
|
||||||
sb.append("Is Open:\t\t").append(mSerialPort.isOpen()).append("\n");
|
|
||||||
|
|
||||||
mLog.info(sb.toString());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mLog.info("No serial port found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes the audio frame decode queue
|
|
||||||
*/
|
|
||||||
public class AudioDecodeProcessor implements Runnable
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
DecodeSpeechRequest request = mDecodeSpeechRequests.poll();
|
|
||||||
|
|
||||||
if(request != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
send(request);
|
|
||||||
}
|
|
||||||
catch(IOException ioe)
|
|
||||||
{
|
|
||||||
mLog.error("Error decoding audio frame", ioe);
|
|
||||||
}
|
|
||||||
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// //Force a 20ms sleep after submitting an audio frame to avoid overflowing the thumbdv
|
|
||||||
// Thread.sleep(20);
|
|
||||||
// }
|
|
||||||
// catch(InterruptedException ie)
|
|
||||||
// {
|
|
||||||
// //Do nothing
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(Throwable t)
|
|
||||||
{
|
|
||||||
mLog.error("Error", t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SerialPortReader implements Runnable
|
|
||||||
{
|
|
||||||
private InputStream mInputStream;
|
|
||||||
|
|
||||||
public SerialPortReader(InputStream inputStream)
|
|
||||||
{
|
|
||||||
mInputStream = inputStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if(mInputStream != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while(mInputStream.available() > 0)
|
|
||||||
{
|
|
||||||
byte[] buffer = new byte[400];
|
|
||||||
|
|
||||||
int bytesRead = mInputStream.readNBytes(buffer, 0, 1);
|
|
||||||
|
|
||||||
if(bytesRead == 1 && buffer[0] == PACKET_START)
|
|
||||||
{
|
|
||||||
while(mInputStream.available() < 2)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Thread.sleep(1);
|
|
||||||
}
|
|
||||||
catch(InterruptedException ie)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bytesRead = mInputStream.readNBytes(buffer, 1, 2);
|
|
||||||
|
|
||||||
if(bytesRead == 2)
|
|
||||||
{
|
|
||||||
int length = (0xFF & buffer[1]) << 8;
|
|
||||||
length += (0xFF & buffer[2]);
|
|
||||||
|
|
||||||
if(0 < length && length < 400)
|
|
||||||
{
|
|
||||||
length++; //Add a byte for the type byte
|
|
||||||
|
|
||||||
while(mInputStream.available() < length)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Thread.sleep(1);
|
|
||||||
}
|
|
||||||
catch(InterruptedException ie)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bytesRead = mInputStream.readNBytes(buffer, 3, length);
|
|
||||||
|
|
||||||
if(bytesRead == length)
|
|
||||||
{
|
|
||||||
receive(Arrays.copyOf(buffer, length + 3));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mLog.debug("Expected [" + length + "] but received [" + bytesRead + "] bytes - " + Arrays.toString(buffer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mLog.error("Received packet with unexpected length: " + length);
|
|
||||||
//Don't process the buffer ... let the reader read and inspect 1 byte at a time
|
|
||||||
//to regain sync on the packet start byte
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mLog.debug("Expected [2] but received [" + bytesRead + "] -- this shouldn't happen");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mLog.debug("Unrecognized byte: " + Arrays.toString(buffer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(IOException ioe)
|
|
||||||
{
|
|
||||||
mLog.error("Error while reading ThumbDv serial port", ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(Throwable t)
|
|
||||||
{
|
|
||||||
mLog.error("Error", t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
mLog.debug("Starting");
|
|
||||||
|
|
||||||
String silence = "BEDDEA821EFD660C08";
|
|
||||||
|
|
||||||
String[] frames = {"0E46122323067C60F8", "0E469433C1067CF1BC", "0E46122B23067C60F8", "0E67162BE08874E2B4",
|
|
||||||
"0E46163BE1067CF1BC", "0E46122B23067C60F8", "0A06163BE00A5C303E", "0E46122B23067C60F8", "0E46163BE1847CE1FC",
|
|
||||||
"0E46122B23067C60F8"};
|
|
||||||
|
|
||||||
List<byte[]> frameData = new ArrayList<>();
|
|
||||||
|
|
||||||
for(String frame : frames)
|
|
||||||
{
|
|
||||||
byte[] bytes = new byte[frame.length() / 2];
|
|
||||||
for(int x = 0; x < frame.length(); x += 2)
|
|
||||||
{
|
|
||||||
String hex = frame.substring(x, x + 2);
|
|
||||||
bytes[x / 2] = (byte) (0xFF & Integer.parseInt(hex, 16));
|
|
||||||
}
|
|
||||||
|
|
||||||
frameData.add(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
mLog.debug("Starting thumb dv thread(s)");
|
|
||||||
|
|
||||||
final Listener<float[]> listener = packet -> {
|
|
||||||
mLog.info("Got an audio packet!");
|
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AMBE-3000R Message
|
|
||||||
*/
|
|
||||||
public abstract class AmbeMessage
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Converts the byte array to a hexadecimal string array
|
|
||||||
*/
|
|
||||||
public static String toHex(byte[] data)
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
for(byte b: data)
|
|
||||||
{
|
|
||||||
sb.append(String.format("%02X", b));
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.DecodeSpeechResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.EncodeSpeechResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.GetConfigResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.InitializeCodecResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.ProductIdResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.ReadyResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetChannelFormatResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetChannelResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetPacketModeResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetSpeechFormatResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderParameterResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.SetVocoderResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.UnknownResponse;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.response.VersionResponse;
|
|
||||||
|
|
||||||
public class AmbeMessageFactory
|
|
||||||
{
|
|
||||||
private static final byte CONTROL_PACKET = (byte)0x00;
|
|
||||||
private static final byte CHANNEL_PACKET = (byte)0x01;
|
|
||||||
private static final byte SPEECH_PACKET = (byte)0x02;
|
|
||||||
private static final int INDEX_PACKET_TYPE = 3;
|
|
||||||
private static final int INDEX_CONTROL_PACKET_TYPE = 4;
|
|
||||||
|
|
||||||
|
|
||||||
public static AmbeMessage getMessage(byte[] data)
|
|
||||||
{
|
|
||||||
if(data != null && data.length >= 5)
|
|
||||||
{
|
|
||||||
if(data[INDEX_PACKET_TYPE] == CONTROL_PACKET)
|
|
||||||
{
|
|
||||||
PacketField packetField = PacketField.fromValue(data[INDEX_CONTROL_PACKET_TYPE]);
|
|
||||||
|
|
||||||
switch(packetField)
|
|
||||||
{
|
|
||||||
case PKT_CHANNEL_0:
|
|
||||||
return new SetChannelResponse(data);
|
|
||||||
case PKT_CHANNEL_FORMAT:
|
|
||||||
return new SetChannelFormatResponse(data);
|
|
||||||
case PKT_CODEC_STOP:
|
|
||||||
return new SetPacketModeResponse(data);
|
|
||||||
case PKT_GET_CONFIG:
|
|
||||||
return new GetConfigResponse(data);
|
|
||||||
case PKT_INIT:
|
|
||||||
return new InitializeCodecResponse(data);
|
|
||||||
case PKT_PRODUCT_ID:
|
|
||||||
return new ProductIdResponse(data);
|
|
||||||
case PKT_RATE_PARAMETER:
|
|
||||||
return new SetVocoderParameterResponse(data);
|
|
||||||
case PKT_RATE_TABLE:
|
|
||||||
return new SetVocoderResponse(data);
|
|
||||||
case PKT_READY:
|
|
||||||
return new ReadyResponse(data);
|
|
||||||
case PKT_SPEECH_FORMAT:
|
|
||||||
return new SetSpeechFormatResponse(data);
|
|
||||||
case PKT_VERSION_STRING:
|
|
||||||
return new VersionResponse(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(data[INDEX_PACKET_TYPE] == CHANNEL_PACKET)
|
|
||||||
{
|
|
||||||
return new EncodeSpeechResponse(data);
|
|
||||||
}
|
|
||||||
else if(data[INDEX_PACKET_TYPE] == SPEECH_PACKET)
|
|
||||||
{
|
|
||||||
return new DecodeSpeechResponse(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new UnknownResponse(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AMBE-3000 Initialization Options
|
|
||||||
*/
|
|
||||||
public enum InitializeOption
|
|
||||||
{
|
|
||||||
ENCODER((byte)0x01),
|
|
||||||
DECODER((byte)0x02),
|
|
||||||
ENCODER_AND_DECODER((byte)0x03),
|
|
||||||
ECHO_CANCELLER((byte)0x04),
|
|
||||||
ENCODER_DECODER_ECHO_CANCELLER((byte)0x07);
|
|
||||||
|
|
||||||
private byte mCode;
|
|
||||||
|
|
||||||
InitializeOption(byte code)
|
|
||||||
{
|
|
||||||
mCode = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte getCode()
|
|
||||||
{
|
|
||||||
return mCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AMBE-3000R Control Packet Field enumeration
|
|
||||||
*/
|
|
||||||
public enum PacketField
|
|
||||||
{
|
|
||||||
PKT_ENCODER_CMODE((byte)0x05),
|
|
||||||
PKT_DECODER_CMODE((byte)0x06),
|
|
||||||
PKT_RATE_TABLE((byte)0x09),
|
|
||||||
PKT_RATE_PARAMETER((byte)0x0A),
|
|
||||||
PKT_INIT((byte)0x0B),
|
|
||||||
PKT_LOW_POWER((byte)0x10),
|
|
||||||
PKT_CHANNEL_FORMAT((byte)0x15),
|
|
||||||
PKT_SPEECH_FORMAT((byte)0x16),
|
|
||||||
PKT_CODEC_START((byte)0x2A),
|
|
||||||
PKT_CODEC_STOP((byte)0x2B),
|
|
||||||
PKT_PRODUCT_ID((byte)0x30),
|
|
||||||
PKT_VERSION_STRING((byte)0x31),
|
|
||||||
PKT_COMPAND((byte)0x32),
|
|
||||||
PKT_RESET((byte)0x33),
|
|
||||||
PKT_RESET_TO_SOFTWARE_CONFIG((byte)0x34),
|
|
||||||
PKT_HALT((byte)0x35),
|
|
||||||
PKT_GET_CONFIG((byte)0x36),
|
|
||||||
PKT_READ_CONFIG((byte)0x37),
|
|
||||||
PKT_CODEC_CFG((byte)0x38),
|
|
||||||
PKT_READY((byte)0x39),
|
|
||||||
PKT_PARITY_MODE((byte)0x3F),
|
|
||||||
PKT_CHANNEL_0((byte)0x40),
|
|
||||||
PKT_WRITE_I2C((byte)0x44),
|
|
||||||
PKT_CLEAR_CODEC_RESET((byte)0x46),
|
|
||||||
PKT_SET_CODEC_RESET((byte)0x47),
|
|
||||||
PKT_DISCARD_CODEC_SAMPLES((byte)0x48),
|
|
||||||
PKT_DELAY_NUMBER_MICRO_SECONDS((byte)0x49),
|
|
||||||
PKT_DELAY_NUMBER_NANO_SECONDS((byte)0x4A),
|
|
||||||
PKT_GAIN((byte)0x4B),
|
|
||||||
PKT_RTS_THRESHOLD((byte)0x4E),
|
|
||||||
|
|
||||||
//Speech packet values
|
|
||||||
SPEECH_DATA((byte)0x00),
|
|
||||||
CHANNEL_DATA_HARD_SYMBOL((byte)0x01),
|
|
||||||
CMODE((byte)0x02),
|
|
||||||
SAMPLE_COUNT((byte)0x03),
|
|
||||||
TONE((byte)0x08),
|
|
||||||
CHANNEL_DATA_SOFT_SYMBOL((byte)0x17),
|
|
||||||
VOCODER((byte)0x40),
|
|
||||||
|
|
||||||
//Packet Types
|
|
||||||
PACKET_TYPE_CONTROL((byte)0x00),
|
|
||||||
PACKET_TYPE_ENCODE_SPEECH((byte)0x01),
|
|
||||||
PACKET_TYPE_DECODE_SPEECH((byte)0x02),
|
|
||||||
|
|
||||||
UNKNOWN((byte)0x00);
|
|
||||||
|
|
||||||
private byte mCode;
|
|
||||||
|
|
||||||
PacketField(byte code)
|
|
||||||
{
|
|
||||||
mCode = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Packet type byte code value
|
|
||||||
*/
|
|
||||||
public byte getCode()
|
|
||||||
{
|
|
||||||
return mCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup a packet type from the code byte value
|
|
||||||
*/
|
|
||||||
public static PacketField fromValue(byte value)
|
|
||||||
{
|
|
||||||
switch(value)
|
|
||||||
{
|
|
||||||
case (byte)0x05:
|
|
||||||
return PKT_ENCODER_CMODE;
|
|
||||||
case (byte)0x06:
|
|
||||||
return PKT_DECODER_CMODE;
|
|
||||||
case (byte)0x09:
|
|
||||||
return PKT_RATE_TABLE;
|
|
||||||
case (byte)0x0A:
|
|
||||||
return PKT_RATE_PARAMETER;
|
|
||||||
case (byte)0x0B:
|
|
||||||
return PKT_INIT;
|
|
||||||
case (byte)0x10:
|
|
||||||
return PKT_LOW_POWER;
|
|
||||||
case (byte)0x15:
|
|
||||||
return PKT_CHANNEL_FORMAT;
|
|
||||||
case (byte)0x16:
|
|
||||||
return PKT_SPEECH_FORMAT;
|
|
||||||
case (byte)0x2A:
|
|
||||||
return PKT_CODEC_START;
|
|
||||||
case (byte)0x2B:
|
|
||||||
return PKT_CODEC_STOP;
|
|
||||||
case (byte)0x30:
|
|
||||||
return PKT_PRODUCT_ID;
|
|
||||||
case (byte)0x31:
|
|
||||||
return PKT_VERSION_STRING;
|
|
||||||
case (byte)0x32:
|
|
||||||
return PKT_COMPAND;
|
|
||||||
case (byte)0x33:
|
|
||||||
return PKT_RESET;
|
|
||||||
case (byte)0x34:
|
|
||||||
return PKT_RESET_TO_SOFTWARE_CONFIG;
|
|
||||||
case (byte)0x35:
|
|
||||||
return PKT_HALT;
|
|
||||||
case (byte)0x36:
|
|
||||||
return PKT_GET_CONFIG;
|
|
||||||
case (byte)0x37:
|
|
||||||
return PKT_READ_CONFIG;
|
|
||||||
case (byte)0x38:
|
|
||||||
return PKT_CODEC_CFG;
|
|
||||||
case (byte)0x39:
|
|
||||||
return PKT_READY;
|
|
||||||
case (byte)0x3F:
|
|
||||||
return PKT_PARITY_MODE;
|
|
||||||
case (byte)0x40:
|
|
||||||
return PKT_CHANNEL_0;
|
|
||||||
case (byte)0x44:
|
|
||||||
return PKT_WRITE_I2C;
|
|
||||||
case (byte)0x46:
|
|
||||||
return PKT_CLEAR_CODEC_RESET;
|
|
||||||
case (byte)0x47:
|
|
||||||
return PKT_SET_CODEC_RESET;
|
|
||||||
case (byte)0x48:
|
|
||||||
return PKT_DISCARD_CODEC_SAMPLES;
|
|
||||||
case (byte)0x49:
|
|
||||||
return PKT_DELAY_NUMBER_MICRO_SECONDS;
|
|
||||||
case (byte)0x4A:
|
|
||||||
return PKT_DELAY_NUMBER_NANO_SECONDS;
|
|
||||||
case (byte)0x4B:
|
|
||||||
return PKT_GAIN;
|
|
||||||
case (byte)0x4E:
|
|
||||||
return PKT_RTS_THRESHOLD;
|
|
||||||
default:
|
|
||||||
return UNKNOWN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message;
|
|
||||||
|
|
||||||
public enum VocoderRate
|
|
||||||
{
|
|
||||||
//AMBE-1000 Standard Rates
|
|
||||||
RATE_0((byte)0x00, 2400,0),
|
|
||||||
RATE_1((byte)0x01, 3600,0),
|
|
||||||
RATE_2((byte)0x02, 3600,1200),
|
|
||||||
RATE_3((byte)0x03, 4800,0),
|
|
||||||
RATE_4((byte)0x04, 9600,0),
|
|
||||||
RATE_5((byte)0x05, 2350,50),
|
|
||||||
RATE_6((byte)0x06, 4850,4750),
|
|
||||||
RATE_7((byte)0x07, 4550,250),
|
|
||||||
RATE_8((byte)0x08, 3100,1700),
|
|
||||||
RATE_9((byte)0x09, 4400,2800),
|
|
||||||
RATE_10((byte)0x0A, 4150,2250),
|
|
||||||
RATE_11((byte)0x0B, 3350,250),
|
|
||||||
RATE_12((byte)0x0C, 7750,250),
|
|
||||||
RATE_13((byte)0x0D, 4650,3350),
|
|
||||||
RATE_14((byte)0x0E, 3750,250),
|
|
||||||
RATE_15((byte)0x0F, 4000,0),
|
|
||||||
|
|
||||||
//AMBE-2000 Standard Rates
|
|
||||||
RATE_16((byte)0x10, 3600,0),
|
|
||||||
RATE_17((byte)0x11, 4000,0),
|
|
||||||
RATE_18((byte)0x12, 4800,0),
|
|
||||||
RATE_19((byte)0x13, 6400,0),
|
|
||||||
RATE_20((byte)0x14, 8000,0),
|
|
||||||
RATE_21((byte)0x15, 9600,0),
|
|
||||||
RATE_22((byte)0x16, 2400,1600),
|
|
||||||
RATE_23((byte)0x17, 3600,1200),
|
|
||||||
RATE_24((byte)0x18, 4000,800),
|
|
||||||
RATE_25((byte)0x19, 2400,2400),
|
|
||||||
RATE_26((byte)0x1A, 4000,2400),
|
|
||||||
RATE_27((byte)0x1B, 4400,2800), //Is this compatible with APCO25 Phase 1?
|
|
||||||
RATE_28((byte)0x1C, 4000,4000),
|
|
||||||
RATE_29((byte)0x1D, 2400,7200),
|
|
||||||
RATE_30((byte)0x1E, 3600,6000),
|
|
||||||
RATE_31((byte)0x1F, 2000,0),
|
|
||||||
RATE_32((byte)0x20, 3600,2800),
|
|
||||||
|
|
||||||
//AMBE-3000 Standard Rates (AMBE+2)
|
|
||||||
RATE_33((byte)0x21, 2450,1150), //APCO25 HR (Phase 2), DMR, NXDN, DSTAR
|
|
||||||
RATE_34((byte)0x22, 2450,0),
|
|
||||||
RATE_35((byte)0x23, 2250,1150),
|
|
||||||
RATE_36((byte)0x24, 2250,0),
|
|
||||||
RATE_37((byte)0x25, 2400,0),
|
|
||||||
RATE_38((byte)0x26, 3000,0),
|
|
||||||
RATE_39((byte)0x27, 3600,0),
|
|
||||||
RATE_40((byte)0x28, 4000,0),
|
|
||||||
RATE_41((byte)0x29, 4400,0),
|
|
||||||
RATE_42((byte)0x2A, 4800,0),
|
|
||||||
RATE_43((byte)0x2B, 6400,0),
|
|
||||||
RATE_44((byte)0x2C, 7200,0),
|
|
||||||
RATE_45((byte)0x2D, 8000,0),
|
|
||||||
RATE_46((byte)0x2E, 9600,0),
|
|
||||||
RATE_47((byte)0x2F, 2450,250),
|
|
||||||
RATE_48((byte)0x30, 3350,250),
|
|
||||||
RATE_49((byte)0x31, 3750,250),
|
|
||||||
RATE_50((byte)0x32, 4550,250),
|
|
||||||
RATE_51((byte)0x33, 2450,1950),
|
|
||||||
RATE_52((byte)0x34, 2450,2350),
|
|
||||||
RATE_53((byte)0x35, 2450,3550),
|
|
||||||
RATE_54((byte)0x36, 2450,4750),
|
|
||||||
RATE_55((byte)0x37, 2600,1400),
|
|
||||||
RATE_56((byte)0x38, 3600,1200),
|
|
||||||
RATE_57((byte)0x39, 4000,800),
|
|
||||||
RATE_58((byte)0x3A, 4000,2400),
|
|
||||||
RATE_59((byte)0x3B, 4400,2800), //Is this compatible with APCO25 Phase 1?
|
|
||||||
RATE_60((byte)0x3C, 4000,4000),
|
|
||||||
RATE_61((byte)0x3D, 3600,6000);
|
|
||||||
|
|
||||||
private byte mCode;
|
|
||||||
private int mSpeechRate;
|
|
||||||
private int mFecRate;
|
|
||||||
|
|
||||||
VocoderRate(byte code, int speechRate, int fecRate)
|
|
||||||
{
|
|
||||||
mCode = code;
|
|
||||||
mSpeechRate = speechRate;
|
|
||||||
mFecRate = fecRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Byte code value for the vocoder rate
|
|
||||||
*/
|
|
||||||
public byte getCode()
|
|
||||||
{
|
|
||||||
return mCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Speech rate in bits per second (bps)
|
|
||||||
*/
|
|
||||||
public int getSpeechRate()
|
|
||||||
{
|
|
||||||
return mSpeechRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Forward Error Correction rate in bits per second (bps)
|
|
||||||
*/
|
|
||||||
public int getFecRate()
|
|
||||||
{
|
|
||||||
return mFecRate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessage;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AMBE-3000R Request Packet
|
|
||||||
*/
|
|
||||||
public abstract class AmbeRequest extends AmbeMessage
|
|
||||||
{
|
|
||||||
private static final byte PACKET_START_BYTE = (byte)0x61;
|
|
||||||
protected static final int PACKET_TYPE_INDEX = 3;
|
|
||||||
protected static final int PAYLOAD_START_INDEX = 4;
|
|
||||||
|
|
||||||
public abstract PacketField getType();
|
|
||||||
public abstract byte[] getData();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a byte array of the specified length plus 4 packet header bytes with packet start, length and control
|
|
||||||
* bytes filled in.
|
|
||||||
*
|
|
||||||
* @param length of the message (not including the 4 byte packet header)
|
|
||||||
* @return byte array with packet header pre-filled.
|
|
||||||
*/
|
|
||||||
protected byte[] createMessage(int length, PacketField type)
|
|
||||||
{
|
|
||||||
byte[] data = new byte[length + 4];
|
|
||||||
|
|
||||||
data[0] = PACKET_START_BYTE;
|
|
||||||
data[1] = (byte)((length >> 8 & 0xFF));
|
|
||||||
data[2] = (byte)(length & 0xFF);
|
|
||||||
|
|
||||||
if(type == PacketField.PACKET_TYPE_ENCODE_SPEECH || type == PacketField.PACKET_TYPE_DECODE_SPEECH)
|
|
||||||
{
|
|
||||||
data[PACKET_TYPE_INDEX] = type.getCode();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
data[PACKET_TYPE_INDEX] = PacketField.PACKET_TYPE_CONTROL.getCode();
|
|
||||||
data[PAYLOAD_START_INDEX] = type.getCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.VocoderRate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode speech request, used to request decode of an encoded audio frame.
|
|
||||||
*/
|
|
||||||
public class DecodeSpeechRequest extends AmbeRequest
|
|
||||||
{
|
|
||||||
private static final int CHANNEL_DATA_IDENTIFIER_INDEX = 4;
|
|
||||||
private static final byte SAMPLE_COUNT = (byte)(0xFF & 160); //8 kHz Audio sample count for 20ms frame
|
|
||||||
|
|
||||||
private byte[] mAudioFrame;
|
|
||||||
private VocoderRate mVocoderRate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an audio frame decode request using the specified vocoder rate.
|
|
||||||
* @param audioFrame of encoded audio samples
|
|
||||||
* @param vocoderRate to use when decoding
|
|
||||||
*/
|
|
||||||
public DecodeSpeechRequest(byte[] audioFrame, VocoderRate vocoderRate)
|
|
||||||
{
|
|
||||||
mAudioFrame = audioFrame;
|
|
||||||
mVocoderRate = vocoderRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an audio frame decode request using the current vocoder rate.
|
|
||||||
* @param audioFrame of encoded audio samples
|
|
||||||
*/
|
|
||||||
public DecodeSpeechRequest(byte[] audioFrame)
|
|
||||||
{
|
|
||||||
this(audioFrame, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PACKET_TYPE_ENCODE_SPEECH;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasVocoderRate()
|
|
||||||
{
|
|
||||||
return mVocoderRate != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getData()
|
|
||||||
{
|
|
||||||
if(hasVocoderRate())
|
|
||||||
{
|
|
||||||
int length = mAudioFrame.length + 9;
|
|
||||||
|
|
||||||
byte[] data = createMessage(length, getType());
|
|
||||||
int offset = CHANNEL_DATA_IDENTIFIER_INDEX;
|
|
||||||
|
|
||||||
data[offset++] = PacketField.VOCODER.getCode();
|
|
||||||
data[offset++] = mVocoderRate.getCode();
|
|
||||||
|
|
||||||
data[offset++] = PacketField.CHANNEL_DATA_HARD_SYMBOL.getCode();
|
|
||||||
data[offset++] = (byte)(0xFF & (mAudioFrame.length * 8));
|
|
||||||
System.arraycopy(mAudioFrame, 0, data, offset, mAudioFrame.length);
|
|
||||||
offset += mAudioFrame.length;
|
|
||||||
data[offset++] = PacketField.SAMPLE_COUNT.getCode();
|
|
||||||
data[offset++] = (byte)0xA0;
|
|
||||||
data[offset++] = (byte)0x02;
|
|
||||||
data[offset++] = (byte)0x00;
|
|
||||||
data[offset] = (byte)0x00;
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int length = mAudioFrame.length + 2;
|
|
||||||
byte[] data = createMessage(length, getType());
|
|
||||||
|
|
||||||
int offset = CHANNEL_DATA_IDENTIFIER_INDEX;
|
|
||||||
data[offset++] = PacketField.CHANNEL_DATA_HARD_SYMBOL.getCode();
|
|
||||||
data[offset++] = (byte)(0xFF & (mAudioFrame.length * 8));
|
|
||||||
System.arraycopy(mAudioFrame, 0, data, offset, mAudioFrame.length);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
public class EncodeSpeechRequest extends AmbeRequest
|
|
||||||
{
|
|
||||||
private static final int CHANNEL_IDENTIFIER_INDEX = 4;
|
|
||||||
private static final int SPEECH_DATA_IDENTIFIER_INDEX = 5;
|
|
||||||
private static final int SAMPLE_COUNT_INDEX = 6;
|
|
||||||
private static final int SPEECH_DATA_START_INDEX = 7;
|
|
||||||
|
|
||||||
private short[] mSamples;
|
|
||||||
|
|
||||||
public EncodeSpeechRequest(short[] samples)
|
|
||||||
{
|
|
||||||
mSamples = samples;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EncodeSpeechRequest(float[] samples)
|
|
||||||
{
|
|
||||||
mSamples = new short[samples.length];
|
|
||||||
|
|
||||||
for(int x = 0; x < samples.length; x++)
|
|
||||||
{
|
|
||||||
if(samples[x] > 1.0f)
|
|
||||||
{
|
|
||||||
mSamples[x] = Short.MAX_VALUE;
|
|
||||||
}
|
|
||||||
else if(samples[x] < -1.0f)
|
|
||||||
{
|
|
||||||
mSamples[x] = Short.MIN_VALUE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mSamples[x] = (short)(samples[x] * Short.MAX_VALUE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PACKET_TYPE_DECODE_SPEECH;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getData()
|
|
||||||
{
|
|
||||||
int length = (mSamples.length * 2) + 3;
|
|
||||||
byte[] data = createMessage(length, getType());
|
|
||||||
|
|
||||||
data[CHANNEL_IDENTIFIER_INDEX] = PacketField.PKT_CHANNEL_0.getCode();
|
|
||||||
data[SPEECH_DATA_IDENTIFIER_INDEX] = (byte)0x00;
|
|
||||||
data[SAMPLE_COUNT_INDEX] = (byte)(0xFF & mSamples.length);
|
|
||||||
|
|
||||||
int pointer = SPEECH_DATA_START_INDEX;
|
|
||||||
|
|
||||||
for(short sample: mSamples)
|
|
||||||
{
|
|
||||||
data[pointer++] = (byte)((sample >> 8 & 0xFF));
|
|
||||||
data[pointer++] = (byte)(sample & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get configuration request packet
|
|
||||||
*/
|
|
||||||
public class GetConfigRequest extends AmbeRequest
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_GET_CONFIG;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getData()
|
|
||||||
{
|
|
||||||
return createMessage(1, getType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.InitializeOption;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize CODEC request packet
|
|
||||||
*/
|
|
||||||
public class InitializeCodecRequest extends AmbeRequest
|
|
||||||
{
|
|
||||||
private static final int INITIALIZE_OPTION_INDEX = 5;
|
|
||||||
private InitializeOption mInitializeOption;
|
|
||||||
|
|
||||||
public InitializeCodecRequest(InitializeOption option)
|
|
||||||
{
|
|
||||||
mInitializeOption = option;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_INIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getData()
|
|
||||||
{
|
|
||||||
byte[] data = createMessage(2, getType());
|
|
||||||
data[INITIALIZE_OPTION_INDEX] = mInitializeOption.getCode();
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Product ID request packet
|
|
||||||
*/
|
|
||||||
public class ProductIdRequest extends AmbeRequest
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_PRODUCT_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getData()
|
|
||||||
{
|
|
||||||
return createMessage(1, getType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset request packet
|
|
||||||
*/
|
|
||||||
public class ResetRequest extends AmbeRequest
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_RESET;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getData()
|
|
||||||
{
|
|
||||||
return createMessage(1, getType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set channel format request packet
|
|
||||||
*/
|
|
||||||
public class SetChannelFormatRequest extends AmbeRequest
|
|
||||||
{
|
|
||||||
private static final int FORMAT_INDEX = 5;
|
|
||||||
private static final byte FORMAT = (byte)0x00;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_CHANNEL_FORMAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getData()
|
|
||||||
{
|
|
||||||
byte[] data = createMessage(2, getType());
|
|
||||||
data[FORMAT_INDEX] = FORMAT;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set channel request packet
|
|
||||||
*/
|
|
||||||
public class SetChannelRequest extends AmbeRequest
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_CHANNEL_0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getData()
|
|
||||||
{
|
|
||||||
return createMessage(1, getType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Packet mode (versus CODEC mode) request packet
|
|
||||||
*/
|
|
||||||
public class SetPacketModeRequest extends AmbeRequest
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_CODEC_STOP;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getData()
|
|
||||||
{
|
|
||||||
return createMessage(1, getType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set speech format request packet
|
|
||||||
*/
|
|
||||||
public class SetSpeechFormatRequest extends AmbeRequest
|
|
||||||
{
|
|
||||||
private static final int FORMAT_INDEX = 5;
|
|
||||||
private static final byte FORMAT = (byte)0x00;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_SPEECH_FORMAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getData()
|
|
||||||
{
|
|
||||||
byte[] data = createMessage(2, getType());
|
|
||||||
data[FORMAT_INDEX] = FORMAT;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set Vocoder parameters request packet
|
|
||||||
*/
|
|
||||||
public class SetVocoderParametersRequest extends AmbeRequest
|
|
||||||
{
|
|
||||||
private int mWord0;
|
|
||||||
private int mWord1;
|
|
||||||
private int mWord2;
|
|
||||||
private int mWord3;
|
|
||||||
private int mWord4;
|
|
||||||
private int mWord5;
|
|
||||||
|
|
||||||
public SetVocoderParametersRequest(int word0, int word1, int word2, int word3, int word4, int word5)
|
|
||||||
{
|
|
||||||
mWord0 = word0;
|
|
||||||
mWord1 = word1;
|
|
||||||
mWord2 = word2;
|
|
||||||
mWord3 = word3;
|
|
||||||
mWord4 = word4;
|
|
||||||
mWord5 = word5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_RATE_PARAMETER;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getData()
|
|
||||||
{
|
|
||||||
byte[] data = createMessage(14, getType());
|
|
||||||
data[PAYLOAD_START_INDEX + 1] = (byte)(mWord0 >> 8 & 0xFF);
|
|
||||||
data[PAYLOAD_START_INDEX + 2] = (byte)(mWord0 & 0xFF);
|
|
||||||
data[PAYLOAD_START_INDEX + 3] = (byte)(mWord1 >> 8 & 0xFF);
|
|
||||||
data[PAYLOAD_START_INDEX + 4] = (byte)(mWord1 & 0xFF);
|
|
||||||
data[PAYLOAD_START_INDEX + 5] = (byte)(mWord2 >> 8 & 0xFF);
|
|
||||||
data[PAYLOAD_START_INDEX + 6] = (byte)(mWord2 & 0xFF);
|
|
||||||
data[PAYLOAD_START_INDEX + 7] = (byte)(mWord3 >> 8 & 0xFF);
|
|
||||||
data[PAYLOAD_START_INDEX + 8] = (byte)(mWord3 & 0xFF);
|
|
||||||
data[PAYLOAD_START_INDEX + 9] = (byte)(mWord4 >> 8 & 0xFF);
|
|
||||||
data[PAYLOAD_START_INDEX + 10] = (byte)(mWord4 & 0xFF);
|
|
||||||
data[PAYLOAD_START_INDEX + 11] = (byte)(mWord5 >> 8 & 0xFF);
|
|
||||||
data[PAYLOAD_START_INDEX + 12] = (byte)(mWord5 & 0xFF);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.VocoderRate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set Vocoder Rate request packet
|
|
||||||
*/
|
|
||||||
public class SetVocoderRequest extends AmbeRequest
|
|
||||||
{
|
|
||||||
private VocoderRate mVocoderRate;
|
|
||||||
|
|
||||||
public SetVocoderRequest(VocoderRate rate)
|
|
||||||
{
|
|
||||||
mVocoderRate = rate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_RATE_TABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getData()
|
|
||||||
{
|
|
||||||
byte[] data = createMessage(2, getType());
|
|
||||||
data[PAYLOAD_START_INDEX + 1] = mVocoderRate.getCode();
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.request;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Version string request packet
|
|
||||||
*/
|
|
||||||
public class VersionRequest extends AmbeRequest
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_VERSION_STRING;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getData()
|
|
||||||
{
|
|
||||||
return createMessage(1, getType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.AmbeMessage;
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AMBE-3000R Response Packet
|
|
||||||
*/
|
|
||||||
public abstract class AmbeResponse extends AmbeMessage
|
|
||||||
{
|
|
||||||
protected static final int PAYLOAD_START_INDEX = 5;
|
|
||||||
private byte[] mMessage;
|
|
||||||
|
|
||||||
protected AmbeResponse(byte[] message)
|
|
||||||
{
|
|
||||||
mMessage = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Control packet type
|
|
||||||
*/
|
|
||||||
public abstract PacketField getType();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Received message bytes
|
|
||||||
*/
|
|
||||||
protected byte[] getMessage()
|
|
||||||
{
|
|
||||||
return mMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Payload of the packet (does not include the packet header)
|
|
||||||
*/
|
|
||||||
protected byte[] getPayload()
|
|
||||||
{
|
|
||||||
return Arrays.copyOfRange(getMessage(), PAYLOAD_START_INDEX, getMessage().length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* sdr-trunk
|
|
||||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
|
||||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
|
||||||
* later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with this program.
|
|
||||||
* If not, see <http://www.gnu.org/licenses/>
|
|
||||||
*
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
import io.github.dsheirer.sample.ConversionUtils;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode speech response
|
|
||||||
*/
|
|
||||||
public class DecodeSpeechResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public DecodeSpeechResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PACKET_TYPE_DECODE_SPEECH;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Payload of the packet (does not include the packet header)
|
|
||||||
*/
|
|
||||||
protected byte[] getPayload()
|
|
||||||
{
|
|
||||||
return Arrays.copyOfRange(getMessage(), PAYLOAD_START_INDEX + 1, getMessage().length);
|
|
||||||
}
|
|
||||||
|
|
||||||
public float[] getSamples()
|
|
||||||
{
|
|
||||||
return ConversionUtils.convertFromSigned16BitSamples(getPayload());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "DECODED SPEECH: SAMPLE COUNT:" + getSamples().length + " " + toHex(getPayload());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode speech response
|
|
||||||
*/
|
|
||||||
public class EncodeSpeechResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public EncodeSpeechResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PACKET_TYPE_ENCODE_SPEECH;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Payload of the packet (does not include the packet header)
|
|
||||||
*/
|
|
||||||
protected byte[] getPayload()
|
|
||||||
{
|
|
||||||
return Arrays.copyOfRange(getMessage(), PAYLOAD_START_INDEX + 1, getMessage().length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "ENCODED SPEECH: " + toHex(getPayload()) + " " + Arrays.toString(getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get configuration response
|
|
||||||
*/
|
|
||||||
public class GetConfigResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public GetConfigResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_GET_CONFIG;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "CONFIGURATION: " + toHex(getPayload());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize CODEC response
|
|
||||||
*/
|
|
||||||
public class InitializeCodecResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public InitializeCodecResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_INIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Success / fail
|
|
||||||
*/
|
|
||||||
public boolean isSuccessful()
|
|
||||||
{
|
|
||||||
byte[] payload = getPayload();
|
|
||||||
|
|
||||||
return payload != null && payload.length ==1 && payload[0] == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "INITIALIZE CODEC " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Product ID Response
|
|
||||||
*/
|
|
||||||
public class ProductIdResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public ProductIdResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_PRODUCT_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Product ID string
|
|
||||||
*/
|
|
||||||
public String getProductId()
|
|
||||||
{
|
|
||||||
return new String(getPayload()).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "PRODUCT ID:" + getProductId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ready response
|
|
||||||
*/
|
|
||||||
public class ReadyResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public ReadyResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_READY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "READY" + Arrays.toString(getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset response
|
|
||||||
*/
|
|
||||||
public class ResetResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public ResetResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_RESET;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Success indicator
|
|
||||||
*/
|
|
||||||
public boolean isSuccessful()
|
|
||||||
{
|
|
||||||
byte[] payload = getPayload();
|
|
||||||
return payload != null && payload.length ==1 && payload[0] == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "RESET " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set channel format response
|
|
||||||
*/
|
|
||||||
public class SetChannelFormatResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public SetChannelFormatResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_CHANNEL_FORMAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Success or fail
|
|
||||||
*/
|
|
||||||
public boolean isSuccessful()
|
|
||||||
{
|
|
||||||
byte[] payload = getPayload();
|
|
||||||
|
|
||||||
return payload != null && payload.length ==1 && payload[0] == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "SET CHANNEL FORMAT " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set Vocoder rate response
|
|
||||||
*/
|
|
||||||
public class SetChannelResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public SetChannelResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_CHANNEL_0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Success indicator
|
|
||||||
*/
|
|
||||||
public boolean isSuccessful()
|
|
||||||
{
|
|
||||||
byte[] payload = getPayload();
|
|
||||||
return payload != null && payload.length ==1 && payload[0] == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "SET CHANNEL 0 " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set packet mode response
|
|
||||||
*/
|
|
||||||
public class SetPacketModeResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public SetPacketModeResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_CODEC_STOP;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Success or fail
|
|
||||||
*/
|
|
||||||
public boolean isSuccessful()
|
|
||||||
{
|
|
||||||
byte[] payload = getPayload();
|
|
||||||
|
|
||||||
return payload != null && payload.length ==1 && payload[0] == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "SET PACKET MODE " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set speech format response
|
|
||||||
*/
|
|
||||||
public class SetSpeechFormatResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public SetSpeechFormatResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_SPEECH_FORMAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Success or fail
|
|
||||||
*/
|
|
||||||
public boolean isSuccessful()
|
|
||||||
{
|
|
||||||
byte[] payload = getPayload();
|
|
||||||
|
|
||||||
return payload != null && payload.length ==1 && payload[0] == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "SET SPEECH FORMAT " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set Vocoder parameter response
|
|
||||||
*/
|
|
||||||
public class SetVocoderParameterResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public SetVocoderParameterResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_RATE_PARAMETER;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Success indicator
|
|
||||||
*/
|
|
||||||
public boolean isSuccessful()
|
|
||||||
{
|
|
||||||
byte[] payload = getPayload();
|
|
||||||
|
|
||||||
return payload != null && payload.length ==3 && payload[0] == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
if(isSuccessful())
|
|
||||||
{
|
|
||||||
return "SET VOCODER RATE SUCCESSFUL";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return "SET VOCODER RATE **FAILED** - RESPONSE:" + toHex(getPayload());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set Vocoder rate response
|
|
||||||
*/
|
|
||||||
public class SetVocoderResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public SetVocoderResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_RATE_TABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Success indicator
|
|
||||||
*/
|
|
||||||
public boolean isSuccessful()
|
|
||||||
{
|
|
||||||
byte[] payload = getPayload();
|
|
||||||
|
|
||||||
return payload != null && payload.length ==1 && payload[0] == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "SET VOCODER RATE " + (isSuccessful() ? "SUCCESSFUL" : "**FAILED**");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unknown Response Message
|
|
||||||
*/
|
|
||||||
public class UnknownResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public UnknownResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "UNKNOWN MESSAGE:" + Arrays.toString(getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* * ******************************************************************************
|
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
* *
|
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.audio.convert.thumbdv.message.response;
|
|
||||||
|
|
||||||
import io.github.dsheirer.audio.convert.thumbdv.message.PacketField;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Version Response
|
|
||||||
*/
|
|
||||||
public class VersionResponse extends AmbeResponse
|
|
||||||
{
|
|
||||||
public VersionResponse(byte[] message)
|
|
||||||
{
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PacketField getType()
|
|
||||||
{
|
|
||||||
return PacketField.PKT_VERSION_STRING;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Version string
|
|
||||||
*/
|
|
||||||
public String getVersion()
|
|
||||||
{
|
|
||||||
return new String(getPayload()).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "VERSION:" + getVersion();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -35,6 +35,7 @@ public enum TunerClass
|
|||||||
LIMESDR("LimeSDR"),
|
LIMESDR("LimeSDR"),
|
||||||
RTL2832("RTL-2832"),
|
RTL2832("RTL-2832"),
|
||||||
RSP("RSP"),
|
RSP("RSP"),
|
||||||
|
SIGNAL_HOUND("Signal Hound"),
|
||||||
TEST_TUNER("Test"),
|
TEST_TUNER("Test"),
|
||||||
RECORDING_TUNER("Recording"),
|
RECORDING_TUNER("Recording"),
|
||||||
UNKNOWN("Unknown" );
|
UNKNOWN("Unknown" );
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import io.github.dsheirer.preference.UserPreferences;
|
|||||||
import io.github.dsheirer.preference.source.ChannelizerType;
|
import io.github.dsheirer.preference.source.ChannelizerType;
|
||||||
import io.github.dsheirer.sdrplay.DeviceInfo;
|
import io.github.dsheirer.sdrplay.DeviceInfo;
|
||||||
import io.github.dsheirer.sdrplay.RspModel;
|
import io.github.dsheirer.sdrplay.RspModel;
|
||||||
|
import io.github.dsheirer.signalhound.DeviceDescriptor;
|
||||||
import io.github.dsheirer.source.SourceException;
|
import io.github.dsheirer.source.SourceException;
|
||||||
import io.github.dsheirer.source.mixer.MixerManager;
|
import io.github.dsheirer.source.mixer.MixerManager;
|
||||||
import io.github.dsheirer.source.tuner.airspy.AirspyTuner;
|
import io.github.dsheirer.source.tuner.airspy.AirspyTuner;
|
||||||
@@ -121,6 +122,11 @@ import io.github.dsheirer.source.tuner.sdrplay.rspDx.IControlRspDx;
|
|||||||
import io.github.dsheirer.source.tuner.sdrplay.rspDx.RspDxTunerConfiguration;
|
import io.github.dsheirer.source.tuner.sdrplay.rspDx.RspDxTunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.sdrplay.rspDx.RspDxTunerController;
|
import io.github.dsheirer.source.tuner.sdrplay.rspDx.RspDxTunerController;
|
||||||
import io.github.dsheirer.source.tuner.sdrplay.rspDx.RspDxTunerEditor;
|
import io.github.dsheirer.source.tuner.sdrplay.rspDx.RspDxTunerEditor;
|
||||||
|
import io.github.dsheirer.source.tuner.signalhound.DiscoveredSignalHoundTuner;
|
||||||
|
import io.github.dsheirer.source.tuner.signalhound.SignalHoundTuner;
|
||||||
|
import io.github.dsheirer.source.tuner.signalhound.SignalHoundTunerConfiguration;
|
||||||
|
import io.github.dsheirer.source.tuner.signalhound.SignalHoundTunerController;
|
||||||
|
import io.github.dsheirer.source.tuner.signalhound.SignalHoundTunerEditor;
|
||||||
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -276,6 +282,20 @@ public class TunerFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Signal Hound tuner
|
||||||
|
* @param descriptor of the device from the Signal Hound API device list
|
||||||
|
* @param channelizerType for the tuner
|
||||||
|
* @param tunerErrorListener to listen for errors from the tuner
|
||||||
|
* @return tuner
|
||||||
|
*/
|
||||||
|
public static SignalHoundTuner getSignalHoundTuner(DeviceDescriptor descriptor, ChannelizerType channelizerType,
|
||||||
|
ITunerErrorListener tunerErrorListener)
|
||||||
|
{
|
||||||
|
return new SignalHoundTuner(new SignalHoundTunerController(descriptor, tunerErrorListener), tunerErrorListener,
|
||||||
|
channelizerType);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a USB tuner
|
* Create a USB tuner
|
||||||
* @param tunerClass to instantiate
|
* @param tunerClass to instantiate
|
||||||
@@ -380,6 +400,8 @@ public class TunerFactory
|
|||||||
return new RspDuoTuner2Configuration(uniqueID);
|
return new RspDuoTuner2Configuration(uniqueID);
|
||||||
case RSP_DX:
|
case RSP_DX:
|
||||||
return new RspDxTunerConfiguration(uniqueID);
|
return new RspDxTunerConfiguration(uniqueID);
|
||||||
|
case SIGNAL_HOUND:
|
||||||
|
return new SignalHoundTunerConfiguration(uniqueID);
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unrecognized tuner type [" + type.name() + "]");
|
throw new IllegalArgumentException("Unrecognized tuner type [" + type.name() + "]");
|
||||||
}
|
}
|
||||||
@@ -448,6 +470,13 @@ public class TunerFactory
|
|||||||
discoveredTuner.getClass());
|
discoveredTuner.getClass());
|
||||||
case RECORDING_TUNER:
|
case RECORDING_TUNER:
|
||||||
return new RecordingTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
return new RecordingTunerEditor(userPreferences, tunerManager, discoveredTuner);
|
||||||
|
case SIGNAL_HOUND:
|
||||||
|
if(discoveredTuner instanceof DiscoveredSignalHoundTuner discoveredSignalHoundTuner)
|
||||||
|
{
|
||||||
|
return new SignalHoundTunerEditor(userPreferences, tunerManager, discoveredSignalHoundTuner);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unrecognized discovered Signal Hound tuner class: " +
|
||||||
|
discoveredTuner.getClass());
|
||||||
case RTL2832:
|
case RTL2832:
|
||||||
if(discoveredTuner.hasTuner())
|
if(discoveredTuner.hasTuner())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ public enum TunerType
|
|||||||
RSP_DUO_1("RSPduo Tuner 1"),
|
RSP_DUO_1("RSPduo Tuner 1"),
|
||||||
RSP_DUO_2("RSPduo Tuner 2"),
|
RSP_DUO_2("RSPduo Tuner 2"),
|
||||||
RSP_DX("RSPdx"),
|
RSP_DX("RSPdx"),
|
||||||
|
SIGNAL_HOUND("Signal Hound"),
|
||||||
|
|
||||||
TEST("Test"),
|
TEST("Test"),
|
||||||
RECORDING("Recording"),
|
RECORDING("Recording"),
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import io.github.dsheirer.source.tuner.rtl.fc2580.FC2580TunerConfiguration;
|
|||||||
import io.github.dsheirer.source.tuner.rtl.r8x.r820t.R820TTunerConfiguration;
|
import io.github.dsheirer.source.tuner.rtl.r8x.r820t.R820TTunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.rtl.r8x.r828d.R828DTunerConfiguration;
|
import io.github.dsheirer.source.tuner.rtl.r8x.r828d.R828DTunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration;
|
import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration;
|
||||||
|
import io.github.dsheirer.source.tuner.signalhound.SignalHoundTunerConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class to hold a configuration for a specific type of tuner
|
* Abstract class to hold a configuration for a specific type of tuner
|
||||||
@@ -62,6 +63,7 @@ import io.github.dsheirer.source.tuner.sdrplay.RspTunerConfiguration;
|
|||||||
@JsonSubTypes.Type(value = R820TTunerConfiguration.class, name = "r820TTunerConfiguration"),
|
@JsonSubTypes.Type(value = R820TTunerConfiguration.class, name = "r820TTunerConfiguration"),
|
||||||
@JsonSubTypes.Type(value = R828DTunerConfiguration.class, name = "r828DTunerConfiguration"),
|
@JsonSubTypes.Type(value = R828DTunerConfiguration.class, name = "r828DTunerConfiguration"),
|
||||||
@JsonSubTypes.Type(value = RspTunerConfiguration.class, name = "rspTunerConfiguration"),
|
@JsonSubTypes.Type(value = RspTunerConfiguration.class, name = "rspTunerConfiguration"),
|
||||||
|
@JsonSubTypes.Type(value = SignalHoundTunerConfiguration.class, name = "signalHoundTunerConfiguration"),
|
||||||
})
|
})
|
||||||
@JacksonXmlRootElement(localName = "tuner_configuration")
|
@JacksonXmlRootElement(localName = "tuner_configuration")
|
||||||
public abstract class TunerConfiguration
|
public abstract class TunerConfiguration
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ import io.github.dsheirer.preference.source.ChannelizerType;
|
|||||||
import io.github.dsheirer.sdrplay.DeviceInfo;
|
import io.github.dsheirer.sdrplay.DeviceInfo;
|
||||||
import io.github.dsheirer.sdrplay.SDRplay;
|
import io.github.dsheirer.sdrplay.SDRplay;
|
||||||
import io.github.dsheirer.sdrplay.SDRplayException;
|
import io.github.dsheirer.sdrplay.SDRplayException;
|
||||||
|
import io.github.dsheirer.signalhound.DeviceDescriptor;
|
||||||
|
import io.github.dsheirer.signalhound.SignalHound;
|
||||||
|
import io.github.dsheirer.signalhound.SignalHoundApi;
|
||||||
|
import io.github.dsheirer.signalhound.SignalHoundException;
|
||||||
import io.github.dsheirer.source.Source;
|
import io.github.dsheirer.source.Source;
|
||||||
import io.github.dsheirer.source.SourceException;
|
import io.github.dsheirer.source.SourceException;
|
||||||
import io.github.dsheirer.source.config.SourceConfigTuner;
|
import io.github.dsheirer.source.config.SourceConfigTuner;
|
||||||
@@ -50,6 +54,7 @@ import io.github.dsheirer.source.tuner.configuration.TunerConfigurationManager;
|
|||||||
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
|
import io.github.dsheirer.source.tuner.recording.RecordingTunerConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.sdrplay.DiscoveredRspTuner;
|
import io.github.dsheirer.source.tuner.sdrplay.DiscoveredRspTuner;
|
||||||
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.DiscoveredRspDuoTuner1;
|
import io.github.dsheirer.source.tuner.sdrplay.rspDuo.DiscoveredRspDuoTuner1;
|
||||||
|
import io.github.dsheirer.source.tuner.signalhound.DiscoveredSignalHoundTuner;
|
||||||
import io.github.dsheirer.source.tuner.ui.DiscoveredTunerModel;
|
import io.github.dsheirer.source.tuner.ui.DiscoveredTunerModel;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -136,6 +141,8 @@ public class TunerManager implements IDiscoveredTunerStatusListener
|
|||||||
discoverSdrPlayTuners();
|
discoverSdrPlayTuners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//The Signal Hound API libraries link their own libusb (and FTDI library for the BB60), so they don't need jlibusb
|
||||||
|
discoverSignalHoundTuners();
|
||||||
discoverRecordingTuners();
|
discoverRecordingTuners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,6 +397,57 @@ public class TunerManager implements IDiscoveredTunerStatusListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discover Signal Hound tuners (BB60, SM200/SM435 and SP145) with each of the Signal Hound API libraries that is
|
||||||
|
* installed. The libraries are from the Signal Hound SDK and are installed separately. Networked (10GbE) SM
|
||||||
|
* devices aren't discovered.
|
||||||
|
*/
|
||||||
|
private void discoverSignalHoundTuners()
|
||||||
|
{
|
||||||
|
ChannelizerType channelizerType = mUserPreferences.getTunerPreference().getChannelizerType();
|
||||||
|
List<String> notInstalled = new ArrayList<>();
|
||||||
|
|
||||||
|
for(SignalHoundApi api : SignalHoundApi.values())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(!api.isAvailable())
|
||||||
|
{
|
||||||
|
notInstalled.add(api.libraryName());
|
||||||
|
mLog.debug("Signal Hound " + api.libraryName() + " library not loaded - " +
|
||||||
|
api.loadFailure().orElse("unknown reason"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.info("Signal Hound " + api.libraryName() + " version [" + api.version() + "] loaded from [" +
|
||||||
|
api.location().orElse("unknown") + "]");
|
||||||
|
|
||||||
|
List<DeviceDescriptor> devices = SignalHound.devices(api);
|
||||||
|
mLog.info("Discovered [" + devices.size() + "] Signal Hound " + api.description() + " devices");
|
||||||
|
|
||||||
|
for(DeviceDescriptor device : devices)
|
||||||
|
{
|
||||||
|
DiscoveredSignalHoundTuner tuner = new DiscoveredSignalHoundTuner(device, channelizerType);
|
||||||
|
|
||||||
|
if(mDiscoveredTunerModel.getDiscoveredTuner(tuner.getId()) == null)
|
||||||
|
{
|
||||||
|
startAndConfigureTuner(tuner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(SignalHoundException | RuntimeException | LinkageError e)
|
||||||
|
{
|
||||||
|
mLog.error("Unable to discover Signal Hound " + api.description() + " devices - " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!notInstalled.isEmpty())
|
||||||
|
{
|
||||||
|
mLog.info("Signal Hound API libraries not installed " + notInstalled + " - Signal Hound tuners for these " +
|
||||||
|
"libraries are not available");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the SDRplay API discovery session.
|
* Closes the SDRplay API discovery session.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.source.tuner.signalhound;
|
||||||
|
|
||||||
|
import io.github.dsheirer.preference.source.ChannelizerType;
|
||||||
|
import io.github.dsheirer.signalhound.DeviceDescriptor;
|
||||||
|
import io.github.dsheirer.source.tuner.TunerClass;
|
||||||
|
import io.github.dsheirer.source.tuner.TunerFactory;
|
||||||
|
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal Hound tuner discovered from the device list of a Signal Hound API.
|
||||||
|
*/
|
||||||
|
public class DiscoveredSignalHoundTuner extends DiscoveredTuner
|
||||||
|
{
|
||||||
|
private final DeviceDescriptor mDescriptor;
|
||||||
|
private final ChannelizerType mChannelizerType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param descriptor of the device, from the Signal Hound API device list
|
||||||
|
* @param channelizerType to use for the tuner
|
||||||
|
*/
|
||||||
|
public DiscoveredSignalHoundTuner(DeviceDescriptor descriptor, ChannelizerType channelizerType)
|
||||||
|
{
|
||||||
|
mDescriptor = descriptor;
|
||||||
|
mChannelizerType = channelizerType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descriptor of the device
|
||||||
|
*/
|
||||||
|
public DeviceDescriptor getDescriptor()
|
||||||
|
{
|
||||||
|
return mDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device family
|
||||||
|
*/
|
||||||
|
public SignalHoundFamily getFamily()
|
||||||
|
{
|
||||||
|
return SignalHoundFamily.of(mDescriptor.api());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TunerClass getTunerClass()
|
||||||
|
{
|
||||||
|
return TunerClass.SIGNAL_HOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs and starts the tuner
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
if(isAvailable() && !hasTuner())
|
||||||
|
{
|
||||||
|
mTuner = TunerFactory.getSignalHoundTuner(mDescriptor, mChannelizerType, this);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mTuner.start();
|
||||||
|
}
|
||||||
|
catch(Exception se)
|
||||||
|
{
|
||||||
|
setErrorMessage("Error starting tuner [" + getId() + "] - " + se.getMessage());
|
||||||
|
mTuner = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tuner ID, which identifies the tuner configuration. The family is used instead of the model, since the APIs
|
||||||
|
* don't always report the model before the device is opened, and the ID must be the same each time.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getId()
|
||||||
|
{
|
||||||
|
return getId(mDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tuner ID of a device.
|
||||||
|
*/
|
||||||
|
public static String getId(DeviceDescriptor descriptor)
|
||||||
|
{
|
||||||
|
return "Signal Hound " + SignalHoundFamily.of(descriptor.api()) + " SER#" + descriptor.serialNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.source.tuner.signalhound;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal Hound device details for display.
|
||||||
|
* @param model such as BB60D, SM200B or SP145
|
||||||
|
* @param serial number
|
||||||
|
* @param firmware version
|
||||||
|
* @param library name of the Signal Hound API library, such as bb_api
|
||||||
|
* @param libraryVersion of the Signal Hound API library
|
||||||
|
* @param libraryLocation where the Signal Hound API library was loaded from
|
||||||
|
*/
|
||||||
|
public record SignalHoundDeviceInformation(String model, int serial, String firmware, String library,
|
||||||
|
String libraryVersion, String libraryLocation)
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.source.tuner.signalhound;
|
||||||
|
|
||||||
|
import io.github.dsheirer.signalhound.SignalHoundApi;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal Hound device families, one for each Signal Hound API, with the limits and front-end controls of each family
|
||||||
|
* from the SDK headers (bb_api.h, sm_api.h and sp_api.h).
|
||||||
|
*
|
||||||
|
* The family names are used in tuner IDs, which persist the tuner configurations, so they must not change.
|
||||||
|
*/
|
||||||
|
public enum SignalHoundFamily
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* BB60A, BB60C and BB60D. Gain (0-3) and attenuation (0-30 dB in 10 dB steps) are automatic unless both are set.
|
||||||
|
*/
|
||||||
|
BB60(SignalHoundApi.BB, "BB60", 40_000_000, 9_000L, 6_400_000_000L, 3, 10, true, false),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SM200A/B/C and SM435B/C. Attenuation 0-30 dB in 5 dB steps, and an RF preselector up to 645 MHz.
|
||||||
|
*/
|
||||||
|
SM(SignalHoundApi.SM, "SM", 50_000_000, 100_000L, 20_600_000_000L, 6, 5, false, true),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SP145A and SP145B. Attenuation 0-30 dB in 5 dB steps, and an RF preselector on the SP145B up to 520 MHz.
|
||||||
|
*/
|
||||||
|
SP145(SignalHoundApi.SP, "SP145", 61_440_000, 9_000L, 15_000_000_000L, 6, 5, false, true);
|
||||||
|
|
||||||
|
//SM435 models tune higher than the SM200
|
||||||
|
private static final long SM435_MAXIMUM_FREQUENCY = 44_200_000_000L;
|
||||||
|
|
||||||
|
private final SignalHoundApi mApi;
|
||||||
|
private final String mLabel;
|
||||||
|
private final int mBaseSampleRate;
|
||||||
|
private final long mMinimumFrequency;
|
||||||
|
private final long mMaximumFrequency;
|
||||||
|
private final int mMaximumAttenuation;
|
||||||
|
private final int mAttenuationStepDb;
|
||||||
|
private final boolean mHasGain;
|
||||||
|
private final boolean mHasPreselector;
|
||||||
|
|
||||||
|
SignalHoundFamily(SignalHoundApi api, String label, int baseSampleRate, long minimumFrequency,
|
||||||
|
long maximumFrequency, int maximumAttenuation, int attenuationStepDb, boolean hasGain,
|
||||||
|
boolean hasPreselector)
|
||||||
|
{
|
||||||
|
mApi = api;
|
||||||
|
mLabel = label;
|
||||||
|
mBaseSampleRate = baseSampleRate;
|
||||||
|
mMinimumFrequency = minimumFrequency;
|
||||||
|
mMaximumFrequency = maximumFrequency;
|
||||||
|
mMaximumAttenuation = maximumAttenuation;
|
||||||
|
mAttenuationStepDb = attenuationStepDb;
|
||||||
|
mHasGain = hasGain;
|
||||||
|
mHasPreselector = hasPreselector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Family of the devices that an API drives.
|
||||||
|
*/
|
||||||
|
public static SignalHoundFamily of(SignalHoundApi api)
|
||||||
|
{
|
||||||
|
return switch(api)
|
||||||
|
{
|
||||||
|
case BB -> BB60;
|
||||||
|
case SM -> SM;
|
||||||
|
case SP -> SP145;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API that drives the family.
|
||||||
|
*/
|
||||||
|
public SignalHoundApi getApi()
|
||||||
|
{
|
||||||
|
return mApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I/Q sample rate before decimation, in Hertz.
|
||||||
|
*/
|
||||||
|
public int getBaseSampleRate()
|
||||||
|
{
|
||||||
|
return mBaseSampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lowest tunable frequency, in Hertz.
|
||||||
|
*/
|
||||||
|
public long getMinimumFrequency()
|
||||||
|
{
|
||||||
|
return mMinimumFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highest tunable frequency of the family's default model, in Hertz.
|
||||||
|
*/
|
||||||
|
public long getMaximumFrequency()
|
||||||
|
{
|
||||||
|
return mMaximumFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highest tunable frequency of a model, in Hertz.
|
||||||
|
* @param model as reported by the device, such as SM435B
|
||||||
|
*/
|
||||||
|
public long getMaximumFrequency(String model)
|
||||||
|
{
|
||||||
|
if(this == SM && model != null && model.toUpperCase(Locale.ROOT).startsWith("SM435"))
|
||||||
|
{
|
||||||
|
return SM435_MAXIMUM_FREQUENCY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMaximumFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highest attenuator setting, where each setting is {@link #getAttenuationStepDb()} dB.
|
||||||
|
*/
|
||||||
|
public int getMaximumAttenuation()
|
||||||
|
{
|
||||||
|
return mMaximumAttenuation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attenuation in dB of each attenuator setting.
|
||||||
|
*/
|
||||||
|
public int getAttenuationStepDb()
|
||||||
|
{
|
||||||
|
return mAttenuationStepDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the family has a manual gain setting, in addition to the attenuator.
|
||||||
|
*/
|
||||||
|
public boolean hasGain()
|
||||||
|
{
|
||||||
|
return mHasGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highest gain setting, for families that have one.
|
||||||
|
*/
|
||||||
|
public int getMaximumGain()
|
||||||
|
{
|
||||||
|
return mHasGain ? 3 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if devices of the family can have an RF preselector. For the SP145, only the SP145B has one.
|
||||||
|
*/
|
||||||
|
public boolean hasPreselector()
|
||||||
|
{
|
||||||
|
return mHasPreselector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.source.tuner.signalhound;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal Hound I/Q sample rate: the family's base sample rate divided by a power of two decimation.
|
||||||
|
* @param decimation power of two
|
||||||
|
* @param rate in Hertz
|
||||||
|
*/
|
||||||
|
public record SignalHoundSampleRate(int decimation, int rate)
|
||||||
|
{
|
||||||
|
//Decimations offered to the user. Higher decimations are supported by the APIs, but the sample rates are below
|
||||||
|
//what is useful for channelizing (600-780 kHz at the highest decimation offered here).
|
||||||
|
public static final int MAXIMUM_DECIMATION = 64;
|
||||||
|
public static final int DEFAULT_RATE = 10_000_000;
|
||||||
|
private static final DecimalFormat FORMAT = new DecimalFormat("0.00###");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sample rates of a family, highest rate first. Every rate is an integer number of Hertz.
|
||||||
|
*/
|
||||||
|
public static List<SignalHoundSampleRate> getRates(SignalHoundFamily family)
|
||||||
|
{
|
||||||
|
List<SignalHoundSampleRate> rates = new ArrayList<>();
|
||||||
|
|
||||||
|
for(int decimation = 1; decimation <= MAXIMUM_DECIMATION; decimation *= 2)
|
||||||
|
{
|
||||||
|
rates.add(new SignalHoundSampleRate(decimation, family.getBaseSampleRate() / decimation));
|
||||||
|
}
|
||||||
|
|
||||||
|
return rates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the highest rate that doesn't exceed the requested rate.
|
||||||
|
* @param rates to search
|
||||||
|
* @param requested rate in Hertz
|
||||||
|
* @return closest rate, or the lowest rate when the requested rate is below all of them
|
||||||
|
*/
|
||||||
|
public static SignalHoundSampleRate getClosest(List<SignalHoundSampleRate> rates, int requested)
|
||||||
|
{
|
||||||
|
SignalHoundSampleRate closest = null;
|
||||||
|
SignalHoundSampleRate lowest = null;
|
||||||
|
|
||||||
|
for(SignalHoundSampleRate rate : rates)
|
||||||
|
{
|
||||||
|
if(rate.rate() <= requested && (closest == null || rate.rate() > closest.rate()))
|
||||||
|
{
|
||||||
|
closest = rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lowest == null || rate.rate() < lowest.rate())
|
||||||
|
{
|
||||||
|
lowest = rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return closest != null ? closest : lowest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return FORMAT.format(rate / 1E6) + " MHz";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.source.tuner.signalhound;
|
||||||
|
|
||||||
|
import io.github.dsheirer.preference.source.ChannelizerType;
|
||||||
|
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||||
|
import io.github.dsheirer.source.tuner.Tuner;
|
||||||
|
import io.github.dsheirer.source.tuner.TunerClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal Hound tuner: BB60, SM200/SM435 or SP145
|
||||||
|
*/
|
||||||
|
public class SignalHoundTuner extends Tuner
|
||||||
|
{
|
||||||
|
//The BB60 and SM200 have 14-bit ADCs. The samples are floats after the API's corrections and filtering.
|
||||||
|
private static final double SAMPLE_SIZE_14_BIT = 14.0;
|
||||||
|
//The API transfers 16-bit I and Q samples from the device (32 bits per complex sample)
|
||||||
|
private static final int LINK_BITS_PER_SAMPLE = 32;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param controller for the Signal Hound device
|
||||||
|
* @param tunerErrorListener to listen for errors from this tuner
|
||||||
|
* @param channelizerType for the channelizer
|
||||||
|
*/
|
||||||
|
public SignalHoundTuner(SignalHoundTunerController controller, ITunerErrorListener tunerErrorListener,
|
||||||
|
ChannelizerType channelizerType)
|
||||||
|
{
|
||||||
|
super(controller, tunerErrorListener, channelizerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal Hound tuner controller
|
||||||
|
*/
|
||||||
|
public SignalHoundTunerController getController()
|
||||||
|
{
|
||||||
|
return (SignalHoundTunerController)getTunerController();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPreferredName()
|
||||||
|
{
|
||||||
|
SignalHoundDeviceInformation info = getController().getDeviceInfo();
|
||||||
|
String model = info != null ? info.model() : getController().getFamily().toString();
|
||||||
|
return "Signal Hound " + model + " " + getUniqueID();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUniqueID()
|
||||||
|
{
|
||||||
|
return Integer.toString(getController().getDescriptor().serialNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TunerClass getTunerClass()
|
||||||
|
{
|
||||||
|
return TunerClass.SIGNAL_HOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getSampleSize()
|
||||||
|
{
|
||||||
|
return SAMPLE_SIZE_14_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaximumUSBBitsPerSecond()
|
||||||
|
{
|
||||||
|
return getController().getFamily().getBaseSampleRate() * LINK_BITS_PER_SAMPLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.source.tuner.signalhound;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.source.tuner.TunerType;
|
||||||
|
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal Hound tuner configuration, for devices of all Signal Hound families.
|
||||||
|
*/
|
||||||
|
public class SignalHoundTunerConfiguration extends TunerConfiguration
|
||||||
|
{
|
||||||
|
private int mSampleRate = SignalHoundSampleRate.DEFAULT_RATE;
|
||||||
|
private int mReferenceLevel = SignalHoundTunerController.REFERENCE_LEVEL_DEFAULT;
|
||||||
|
private int mAttenuation = SignalHoundTunerController.AUTOMATIC;
|
||||||
|
private int mGain = SignalHoundTunerController.AUTOMATIC;
|
||||||
|
private boolean mPreselector = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor for Jackson
|
||||||
|
*/
|
||||||
|
public SignalHoundTunerConfiguration()
|
||||||
|
{
|
||||||
|
super(SignalHoundTunerController.MINIMUM_TUNABLE_FREQUENCY_HZ,
|
||||||
|
SignalHoundTunerController.MAXIMUM_TUNABLE_FREQUENCY_HZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param uniqueID for the tuner
|
||||||
|
*/
|
||||||
|
public SignalHoundTunerConfiguration(String uniqueID)
|
||||||
|
{
|
||||||
|
super(uniqueID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||||
|
public TunerType getTunerType()
|
||||||
|
{
|
||||||
|
return TunerType.SIGNAL_HOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sample rate in Hertz
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "sample_rate")
|
||||||
|
public int getSampleRate()
|
||||||
|
{
|
||||||
|
return mSampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSampleRate(int sampleRate)
|
||||||
|
{
|
||||||
|
mSampleRate = sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference level in dBm: the strongest expected input level, which sets the gain and attenuation automatically.
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "reference_level")
|
||||||
|
public int getReferenceLevel()
|
||||||
|
{
|
||||||
|
return mReferenceLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReferenceLevel(int referenceLevel)
|
||||||
|
{
|
||||||
|
mReferenceLevel = referenceLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attenuator setting, or -1 for automatic. The step size depends on the device family.
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "attenuation")
|
||||||
|
public int getAttenuation()
|
||||||
|
{
|
||||||
|
return mAttenuation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttenuation(int attenuation)
|
||||||
|
{
|
||||||
|
mAttenuation = attenuation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gain setting (BB60 only), or -1 for automatic.
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "gain")
|
||||||
|
public int getGain()
|
||||||
|
{
|
||||||
|
return mGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGain(int gain)
|
||||||
|
{
|
||||||
|
mGain = gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RF preselector (SM200/SM435 and SP145B) enabled.
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "preselector")
|
||||||
|
public boolean isPreselector()
|
||||||
|
{
|
||||||
|
return mPreselector;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPreselector(boolean preselector)
|
||||||
|
{
|
||||||
|
mPreselector = preselector;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,534 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.source.tuner.signalhound;
|
||||||
|
|
||||||
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
|
import io.github.dsheirer.signalhound.Diagnostics;
|
||||||
|
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||||
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
|
import io.github.dsheirer.source.tuner.manager.TunerStatus;
|
||||||
|
import io.github.dsheirer.source.tuner.ui.TunerEditor;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.DefaultComboBoxModel;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JCheckBox;
|
||||||
|
import javax.swing.JComboBox;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JSeparator;
|
||||||
|
import javax.swing.JSlider;
|
||||||
|
import javax.swing.SpinnerNumberModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal Hound tuner editor
|
||||||
|
*/
|
||||||
|
public class SignalHoundTunerEditor extends TunerEditor<SignalHoundTuner, SignalHoundTunerConfiguration>
|
||||||
|
{
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(SignalHoundTunerEditor.class);
|
||||||
|
private final SignalHoundFamily mFamily;
|
||||||
|
private JButton mTunerInfoButton;
|
||||||
|
private JComboBox<SignalHoundSampleRate> mSampleRateCombo;
|
||||||
|
private JSlider mReferenceLevelSlider;
|
||||||
|
private JLabel mReferenceLevelValueLabel;
|
||||||
|
private JComboBox<Setting> mAttenuationCombo;
|
||||||
|
private JComboBox<Setting> mGainCombo;
|
||||||
|
private JCheckBox mPreselectorCheckBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param userPreferences for wide-band recordings
|
||||||
|
* @param tunerManager for saving tuner configuration
|
||||||
|
* @param discoveredTuner for the optionally usable Signal Hound tuner and controller
|
||||||
|
*/
|
||||||
|
public SignalHoundTunerEditor(UserPreferences userPreferences, TunerManager tunerManager,
|
||||||
|
DiscoveredSignalHoundTuner discoveredTuner)
|
||||||
|
{
|
||||||
|
super(userPreferences, tunerManager, discoveredTuner);
|
||||||
|
mFamily = discoveredTuner.getFamily();
|
||||||
|
init();
|
||||||
|
tunerStatusUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMinimumTunableFrequency()
|
||||||
|
{
|
||||||
|
return hasTuner() ? getTuner().getController().getMinimumTunableFrequency() :
|
||||||
|
SignalHoundTunerController.MINIMUM_TUNABLE_FREQUENCY_HZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMaximumTunableFrequency()
|
||||||
|
{
|
||||||
|
return hasTuner() ? getTuner().getController().getMaximumTunableFrequency() :
|
||||||
|
SignalHoundTunerController.MAXIMUM_TUNABLE_FREQUENCY_HZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init()
|
||||||
|
{
|
||||||
|
setLayout(new MigLayout("fill,wrap 3", "[right][grow,fill][fill]", "[][][][][][][][][][][][grow]"));
|
||||||
|
|
||||||
|
add(new JLabel("Tuner:"));
|
||||||
|
add(getTunerIdLabel());
|
||||||
|
add(getTunerInfoButton());
|
||||||
|
|
||||||
|
add(new JLabel("Status:"));
|
||||||
|
add(getTunerStatusLabel(), "wrap");
|
||||||
|
|
||||||
|
add(getButtonPanel(), "span,align left");
|
||||||
|
|
||||||
|
add(new JSeparator(), "span,growx");
|
||||||
|
|
||||||
|
add(new JLabel("Frequency (MHz):"));
|
||||||
|
add(getFrequencyPanel(), "wrap");
|
||||||
|
|
||||||
|
add(new JLabel("Sample Rate:"));
|
||||||
|
add(getSampleRateCombo(), "wrap");
|
||||||
|
|
||||||
|
add(new JSeparator(), "span,growx");
|
||||||
|
|
||||||
|
add(new JLabel("Reference Level:"));
|
||||||
|
add(getReferenceLevelSlider());
|
||||||
|
add(getReferenceLevelValueLabel());
|
||||||
|
|
||||||
|
add(new JLabel("Attenuation:"));
|
||||||
|
add(getAttenuationCombo(), "wrap");
|
||||||
|
|
||||||
|
if(mFamily.hasGain())
|
||||||
|
{
|
||||||
|
add(new JLabel("Gain:"));
|
||||||
|
add(getGainCombo(), "wrap");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mFamily.hasPreselector())
|
||||||
|
{
|
||||||
|
add(new JLabel(""));
|
||||||
|
add(getPreselectorCheckBox(), "wrap");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Empty row that takes all extra vertical space so that the controls stay stacked at the top of the editor.
|
||||||
|
add(new JLabel(), "span,pushy");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void tunerStatusUpdated()
|
||||||
|
{
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
boolean started = hasStartedTuner();
|
||||||
|
getTunerIdLabel().setText(started ? getTuner().getPreferredName() : getDiscoveredTuner().getId());
|
||||||
|
|
||||||
|
String status = getDiscoveredTuner().getTunerStatus().toString();
|
||||||
|
if(getDiscoveredTuner().hasErrorMessage())
|
||||||
|
{
|
||||||
|
status += " - " + getDiscoveredTuner().getErrorMessage();
|
||||||
|
}
|
||||||
|
getTunerStatusLabel().setText(status);
|
||||||
|
getButtonPanel().updateControls();
|
||||||
|
getFrequencyPanel().updateControls();
|
||||||
|
getTunerInfoButton().setEnabled(started);
|
||||||
|
|
||||||
|
if(started)
|
||||||
|
{
|
||||||
|
SignalHoundTunerController controller = getTuner().getController();
|
||||||
|
List<SignalHoundSampleRate> rates = controller.getSampleRates();
|
||||||
|
getSampleRateCombo().setModel(new DefaultComboBoxModel<>(rates.toArray(new SignalHoundSampleRate[0])));
|
||||||
|
getSampleRateCombo().setSelectedItem(controller.getSignalHoundSampleRate());
|
||||||
|
getSampleRateCombo().setEnabled(!controller.isLockedSampleRate());
|
||||||
|
|
||||||
|
getReferenceLevelSlider().setValue(controller.getReferenceLevel());
|
||||||
|
getReferenceLevelSlider().setEnabled(true);
|
||||||
|
getReferenceLevelValueLabel().setEnabled(true);
|
||||||
|
|
||||||
|
getAttenuationCombo().setSelectedItem(new Setting(controller.getAttenuation(), null));
|
||||||
|
getAttenuationCombo().setEnabled(true);
|
||||||
|
|
||||||
|
if(mFamily.hasGain())
|
||||||
|
{
|
||||||
|
getGainCombo().setSelectedItem(new Setting(controller.getGain(), null));
|
||||||
|
getGainCombo().setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mFamily.hasPreselector())
|
||||||
|
{
|
||||||
|
getPreselectorCheckBox().setSelected(controller.isPreselector());
|
||||||
|
getPreselectorCheckBox().setEnabled(controller.isPreselectorSupported());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getSampleRateCombo().setModel(new DefaultComboBoxModel<>());
|
||||||
|
getSampleRateCombo().setEnabled(false);
|
||||||
|
getReferenceLevelSlider().setEnabled(false);
|
||||||
|
getReferenceLevelValueLabel().setEnabled(false);
|
||||||
|
getAttenuationCombo().setEnabled(false);
|
||||||
|
|
||||||
|
if(mFamily.hasGain())
|
||||||
|
{
|
||||||
|
getGainCombo().setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mFamily.hasPreselector())
|
||||||
|
{
|
||||||
|
getPreselectorCheckBox().setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateReferenceLevelLabel();
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the tuner has finished starting, since a status update can arrive while it's starting or restarting.
|
||||||
|
*/
|
||||||
|
private boolean hasStartedTuner()
|
||||||
|
{
|
||||||
|
return hasTuner() && getTuner().getController().getSignalHoundSampleRate() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateReferenceLevelLabel()
|
||||||
|
{
|
||||||
|
getReferenceLevelValueLabel().setText(getReferenceLevelSlider().getValue() + " dBm");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button that provides tuner information
|
||||||
|
*/
|
||||||
|
private JButton getTunerInfoButton()
|
||||||
|
{
|
||||||
|
if(mTunerInfoButton == null)
|
||||||
|
{
|
||||||
|
mTunerInfoButton = new JButton("Info");
|
||||||
|
mTunerInfoButton.setEnabled(false);
|
||||||
|
mTunerInfoButton.addActionListener(_ -> JOptionPane.showMessageDialog(SignalHoundTunerEditor.this,
|
||||||
|
getTunerInfo(), "Tuner Info", JOptionPane.INFORMATION_MESSAGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTunerInfoButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JComboBox<SignalHoundSampleRate> getSampleRateCombo()
|
||||||
|
{
|
||||||
|
if(mSampleRateCombo == null)
|
||||||
|
{
|
||||||
|
mSampleRateCombo = new JComboBox<>();
|
||||||
|
mSampleRateCombo.setEnabled(false);
|
||||||
|
mSampleRateCombo.addActionListener(_ ->
|
||||||
|
{
|
||||||
|
if(hasTuner() && !isLoading())
|
||||||
|
{
|
||||||
|
SignalHoundSampleRate rate = (SignalHoundSampleRate)mSampleRateCombo.getSelectedItem();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
getTuner().getController().setSampleRate(rate);
|
||||||
|
|
||||||
|
//Adjust the min/max values for the sample rate.
|
||||||
|
adjustForSampleRate(rate.rate());
|
||||||
|
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
catch(Exception e1)
|
||||||
|
{
|
||||||
|
mLog.error("Error setting Signal Hound sample rate", e1);
|
||||||
|
JOptionPane.showMessageDialog(SignalHoundTunerEditor.this, "Couldn't set sample rate to " +
|
||||||
|
rate + " - " + e1.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSampleRateCombo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSlider getReferenceLevelSlider()
|
||||||
|
{
|
||||||
|
if(mReferenceLevelSlider == null)
|
||||||
|
{
|
||||||
|
mReferenceLevelSlider = new JSlider(JSlider.HORIZONTAL, SignalHoundTunerController.REFERENCE_LEVEL_MINIMUM,
|
||||||
|
SignalHoundTunerController.REFERENCE_LEVEL_MAXIMUM,
|
||||||
|
SignalHoundTunerController.REFERENCE_LEVEL_DEFAULT);
|
||||||
|
mReferenceLevelSlider.setEnabled(false);
|
||||||
|
mReferenceLevelSlider.setMajorTickSpacing(10);
|
||||||
|
mReferenceLevelSlider.setPaintTicks(true);
|
||||||
|
mReferenceLevelSlider.setToolTipText("Strongest expected input level. Lower values increase the gain. " +
|
||||||
|
"Raise it when the log reports ADC overflow warnings.");
|
||||||
|
mReferenceLevelSlider.addChangeListener(_ ->
|
||||||
|
{
|
||||||
|
updateReferenceLevelLabel();
|
||||||
|
|
||||||
|
//Reconfigure the stream once the user releases the slider, since each change restarts the stream
|
||||||
|
if(hasTuner() && !isLoading() && !mReferenceLevelSlider.getValueIsAdjusting())
|
||||||
|
{
|
||||||
|
int referenceLevel = mReferenceLevelSlider.getValue();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
getTuner().getController().setReferenceLevel(referenceLevel);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't set Signal Hound reference level to:" + referenceLevel, e);
|
||||||
|
JOptionPane.showMessageDialog(mReferenceLevelSlider, "Couldn't set reference level to " +
|
||||||
|
referenceLevel + " dBm");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mReferenceLevelSlider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JLabel getReferenceLevelValueLabel()
|
||||||
|
{
|
||||||
|
if(mReferenceLevelValueLabel == null)
|
||||||
|
{
|
||||||
|
mReferenceLevelValueLabel = new JLabel("0 dBm");
|
||||||
|
mReferenceLevelValueLabel.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mReferenceLevelValueLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JComboBox<Setting> getAttenuationCombo()
|
||||||
|
{
|
||||||
|
if(mAttenuationCombo == null)
|
||||||
|
{
|
||||||
|
List<Setting> settings = new ArrayList<>();
|
||||||
|
settings.add(new Setting(SignalHoundTunerController.AUTOMATIC, "Automatic (Reference Level)"));
|
||||||
|
|
||||||
|
for(int x = 0; x <= mFamily.getMaximumAttenuation(); x++)
|
||||||
|
{
|
||||||
|
settings.add(new Setting(x, (x * mFamily.getAttenuationStepDb()) + " dB"));
|
||||||
|
}
|
||||||
|
|
||||||
|
mAttenuationCombo = new JComboBox<>(settings.toArray(new Setting[0]));
|
||||||
|
mAttenuationCombo.setEnabled(false);
|
||||||
|
mAttenuationCombo.setToolTipText(mFamily.hasGain() ? "The BB60 uses manual attenuation only when the " +
|
||||||
|
"gain is also manual" : "Input attenuation, or automatic to set it from the reference level");
|
||||||
|
mAttenuationCombo.addActionListener(_ ->
|
||||||
|
{
|
||||||
|
if(hasTuner() && !isLoading() && mAttenuationCombo.getSelectedItem() instanceof Setting setting)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
getTuner().getController().setAttenuation(setting.value());
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't set Signal Hound attenuation to:" + setting, e);
|
||||||
|
JOptionPane.showMessageDialog(mAttenuationCombo, "Couldn't set attenuation to " + setting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mAttenuationCombo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JComboBox<Setting> getGainCombo()
|
||||||
|
{
|
||||||
|
if(mGainCombo == null)
|
||||||
|
{
|
||||||
|
List<Setting> settings = new ArrayList<>();
|
||||||
|
settings.add(new Setting(SignalHoundTunerController.AUTOMATIC, "Automatic (Reference Level)"));
|
||||||
|
|
||||||
|
for(int x = 0; x <= mFamily.getMaximumGain(); x++)
|
||||||
|
{
|
||||||
|
settings.add(new Setting(x, Integer.toString(x)));
|
||||||
|
}
|
||||||
|
|
||||||
|
mGainCombo = new JComboBox<>(settings.toArray(new Setting[0]));
|
||||||
|
mGainCombo.setEnabled(false);
|
||||||
|
mGainCombo.setToolTipText("The BB60 uses manual gain only when the attenuation is also manual");
|
||||||
|
mGainCombo.addActionListener(_ ->
|
||||||
|
{
|
||||||
|
if(hasTuner() && !isLoading() && mGainCombo.getSelectedItem() instanceof Setting setting)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
getTuner().getController().setGain(setting.value());
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't set Signal Hound gain to:" + setting, e);
|
||||||
|
JOptionPane.showMessageDialog(mGainCombo, "Couldn't set gain to " + setting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mGainCombo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JCheckBox getPreselectorCheckBox()
|
||||||
|
{
|
||||||
|
if(mPreselectorCheckBox == null)
|
||||||
|
{
|
||||||
|
mPreselectorCheckBox = new JCheckBox("RF Preselector");
|
||||||
|
mPreselectorCheckBox.setEnabled(false);
|
||||||
|
mPreselectorCheckBox.setToolTipText(mFamily == SignalHoundFamily.SM ?
|
||||||
|
"Filters the input to reject strong out-of-band signals, up to 645 MHz" :
|
||||||
|
"SP145B only: filters the input to reject strong out-of-band signals, up to 520 MHz");
|
||||||
|
mPreselectorCheckBox.addActionListener(_ ->
|
||||||
|
{
|
||||||
|
if(hasTuner() && !isLoading())
|
||||||
|
{
|
||||||
|
boolean enabled = mPreselectorCheckBox.isSelected();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
getTuner().getController().setPreselector(enabled);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't " + (enabled ? "enable" : "disable") + " Signal Hound preselector", e);
|
||||||
|
JOptionPane.showMessageDialog(mPreselectorCheckBox, "Couldn't change preselector - " +
|
||||||
|
e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPreselectorCheckBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save()
|
||||||
|
{
|
||||||
|
if(hasConfiguration() && !isLoading())
|
||||||
|
{
|
||||||
|
getConfiguration().setFrequency(getFrequencyControl().getFrequency());
|
||||||
|
getConfiguration().setMinimumFrequency(getMinimumFrequencyTextField().getFrequency());
|
||||||
|
getConfiguration().setMaximumFrequency(getMaximumFrequencyTextField().getFrequency());
|
||||||
|
double value = ((SpinnerNumberModel)getFrequencyCorrectionSpinner().getModel()).getNumber().doubleValue();
|
||||||
|
getConfiguration().setFrequencyCorrection(value);
|
||||||
|
getConfiguration().setAutoPPMCorrectionEnabled(getAutoPPMCheckBox().isSelected());
|
||||||
|
|
||||||
|
if(getSampleRateCombo().getSelectedItem() instanceof SignalHoundSampleRate rate)
|
||||||
|
{
|
||||||
|
getConfiguration().setSampleRate(rate.rate());
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfiguration().setReferenceLevel(getReferenceLevelSlider().getValue());
|
||||||
|
|
||||||
|
if(getAttenuationCombo().getSelectedItem() instanceof Setting setting)
|
||||||
|
{
|
||||||
|
getConfiguration().setAttenuation(setting.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mFamily.hasGain() && getGainCombo().getSelectedItem() instanceof Setting setting)
|
||||||
|
{
|
||||||
|
getConfiguration().setGain(setting.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mFamily.hasPreselector())
|
||||||
|
{
|
||||||
|
getConfiguration().setPreselector(getPreselectorCheckBox().isSelected());
|
||||||
|
}
|
||||||
|
|
||||||
|
saveConfiguration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTunerLockState(boolean locked)
|
||||||
|
{
|
||||||
|
getFrequencyPanel().updateControls();
|
||||||
|
getSampleRateCombo().setEnabled(!locked);
|
||||||
|
getSampleRateCombo().setToolTipText(locked ? "Sample Rate is locked. Disable decoding channels to unlock." :
|
||||||
|
"Select a sample rate for the tuner");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTunerInfo()
|
||||||
|
{
|
||||||
|
if(getDiscoveredTuner().getTunerStatus() == TunerStatus.ERROR)
|
||||||
|
{
|
||||||
|
return getDiscoveredTuner().getErrorMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hasTuner())
|
||||||
|
{
|
||||||
|
SignalHoundDeviceInformation info = getTuner().getController().getDeviceInfo();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("<html><h3>Signal Hound Tuner</h3>");
|
||||||
|
sb.append("<b>Model: </b>").append(info.model()).append("<br>");
|
||||||
|
sb.append("<b>Serial: </b>").append(info.serial()).append("<br>");
|
||||||
|
sb.append("<b>Firmware: </b>").append(info.firmware()).append("<br>");
|
||||||
|
sb.append("<b>Library: </b>").append(info.library()).append(" ").append(info.libraryVersion()).append("<br>");
|
||||||
|
sb.append("<b>Location: </b>").append(info.libraryLocation()).append("<br>");
|
||||||
|
|
||||||
|
Diagnostics diagnostics = getTuner().getController().getDiagnostics();
|
||||||
|
|
||||||
|
if(diagnostics != null)
|
||||||
|
{
|
||||||
|
sb.append("<b>Temperature: </b>").append(String.format(Locale.ROOT, "%.1f C",
|
||||||
|
diagnostics.temperature())).append("<br>");
|
||||||
|
sb.append("<b>Voltage: </b>").append(String.format(Locale.ROOT, "%.2f V",
|
||||||
|
diagnostics.voltage())).append("<br>");
|
||||||
|
sb.append("<b>Current: </b>").append(String.format(Locale.ROOT, "%.3f A",
|
||||||
|
diagnostics.current())).append("<br>");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attenuation or gain combo box entry. Entries are equal when their values are equal, so that the entry for a
|
||||||
|
* value can be selected with a new instance.
|
||||||
|
* @param value of the setting, or -1 for automatic
|
||||||
|
* @param label to display
|
||||||
|
*/
|
||||||
|
record Setting(int value, String label)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
return o instanceof Setting other && other.value == value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return Integer.hashCode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,499 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.source.tuner.signalhound;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.github.dsheirer.buffer.INativeBuffer;
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.signalhound.DeviceDescriptor;
|
||||||
|
import io.github.dsheirer.signalhound.Diagnostics;
|
||||||
|
import io.github.dsheirer.signalhound.IqBlock;
|
||||||
|
import io.github.dsheirer.signalhound.IqBuffer;
|
||||||
|
import io.github.dsheirer.signalhound.IqConfig;
|
||||||
|
import io.github.dsheirer.signalhound.IqDataType;
|
||||||
|
import io.github.dsheirer.signalhound.IqStreamInfo;
|
||||||
|
import io.github.dsheirer.signalhound.SignalHoundApi;
|
||||||
|
import io.github.dsheirer.signalhound.SignalHoundDevice;
|
||||||
|
import io.github.dsheirer.signalhound.SignalHoundException;
|
||||||
|
import io.github.dsheirer.signalhound.Sweep;
|
||||||
|
import io.github.dsheirer.signalhound.SweepConfig;
|
||||||
|
import io.github.dsheirer.signalhound.SweepInfo;
|
||||||
|
import io.github.dsheirer.source.SourceException;
|
||||||
|
import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||||
|
import io.github.dsheirer.source.tuner.TunerFactory;
|
||||||
|
import io.github.dsheirer.source.tuner.TunerType;
|
||||||
|
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||||
|
import java.lang.foreign.ValueLayout;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for the Signal Hound tuner, using a simulated device since the tests can't assume Signal Hound hardware.
|
||||||
|
*/
|
||||||
|
class SignalHoundTunerTest
|
||||||
|
{
|
||||||
|
private static final DeviceDescriptor SM200 = new DeviceDescriptor(SignalHoundApi.SM, 1234, "SM200B");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sampleRatesAreTheBaseRateDecimatedByPowersOfTwo()
|
||||||
|
{
|
||||||
|
List<SignalHoundSampleRate> bb60 = SignalHoundSampleRate.getRates(SignalHoundFamily.BB60);
|
||||||
|
assertEquals(new SignalHoundSampleRate(1, 40_000_000), bb60.getFirst());
|
||||||
|
assertEquals(new SignalHoundSampleRate(64, 625_000), bb60.getLast());
|
||||||
|
assertEquals(new SignalHoundSampleRate(64, 781_250), SignalHoundSampleRate.getRates(SignalHoundFamily.SM).getLast());
|
||||||
|
assertEquals(new SignalHoundSampleRate(64, 960_000), SignalHoundSampleRate.getRates(SignalHoundFamily.SP145).getLast());
|
||||||
|
|
||||||
|
for(SignalHoundFamily family : SignalHoundFamily.values())
|
||||||
|
{
|
||||||
|
for(SignalHoundSampleRate rate : SignalHoundSampleRate.getRates(family))
|
||||||
|
{
|
||||||
|
assertEquals(family.getBaseSampleRate(), (long)rate.rate() * rate.decimation(), "Rate " + rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void closestRateDoesNotExceedTheRequestedRate()
|
||||||
|
{
|
||||||
|
List<SignalHoundSampleRate> bb60 = SignalHoundSampleRate.getRates(SignalHoundFamily.BB60);
|
||||||
|
assertEquals(10_000_000, SignalHoundSampleRate.getClosest(bb60, 10_000_000).rate());
|
||||||
|
assertEquals(6_250_000, SignalHoundSampleRate.getClosest(SignalHoundSampleRate.getRates(SignalHoundFamily.SM),
|
||||||
|
10_000_000).rate());
|
||||||
|
assertEquals(7_680_000, SignalHoundSampleRate.getClosest(
|
||||||
|
SignalHoundSampleRate.getRates(SignalHoundFamily.SP145), 10_000_000).rate());
|
||||||
|
assertEquals(625_000, SignalHoundSampleRate.getClosest(bb60, 1).rate());
|
||||||
|
assertEquals("1.5625 MHz", new SignalHoundSampleRate(32, 1_562_500).toString());
|
||||||
|
assertEquals("10.00 MHz", new SignalHoundSampleRate(4, 10_000_000).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void familyLimits()
|
||||||
|
{
|
||||||
|
assertEquals(SignalHoundFamily.SM, SignalHoundFamily.of(SignalHoundApi.SM));
|
||||||
|
assertEquals(20_600_000_000L, SignalHoundFamily.SM.getMaximumFrequency("SM200B"));
|
||||||
|
assertEquals(44_200_000_000L, SignalHoundFamily.SM.getMaximumFrequency("SM435C"));
|
||||||
|
assertEquals(6_400_000_000L, SignalHoundFamily.BB60.getMaximumFrequency("BB60D"));
|
||||||
|
assertTrue(SignalHoundFamily.BB60.hasGain());
|
||||||
|
assertFalse(SignalHoundFamily.SM.hasGain());
|
||||||
|
assertEquals(SignalHoundTunerController.MAXIMUM_TUNABLE_FREQUENCY_HZ,
|
||||||
|
SignalHoundFamily.SM.getMaximumFrequency("SM435B"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readsAreAbout10MillisecondsAsAPowerOfTwo()
|
||||||
|
{
|
||||||
|
assertEquals(65536, SignalHoundTunerController.getBufferSampleCount(10_000_000));
|
||||||
|
assertEquals(8192, SignalHoundTunerController.getBufferSampleCount(625_000));
|
||||||
|
assertEquals(524288, SignalHoundTunerController.getBufferSampleCount(61_440_000));
|
||||||
|
assertEquals(8192, SignalHoundTunerController.getBufferSampleCount(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void referenceLevelIsFullScale()
|
||||||
|
{
|
||||||
|
//A -20 dBm signal has an amplitude of sqrt(0.01 mW) = 0.1
|
||||||
|
assertEquals(10.0f, SignalHoundTunerController.getSampleScale(-20), 1e-5f);
|
||||||
|
assertEquals(1.0f, SignalHoundTunerController.getSampleScale(0), 1e-6f);
|
||||||
|
assertEquals(0.1f, SignalHoundTunerController.getSampleScale(20), 1e-6f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void usableBandwidthComesFromTheStreamBandwidth()
|
||||||
|
{
|
||||||
|
assertEquals(0.75, SignalHoundTunerController.getUsableBandwidthPercent(
|
||||||
|
new IqStreamInfo(40e6, 30e6, IqDataType.FLOAT32)), 1e-9);
|
||||||
|
assertEquals(0.9, SignalHoundTunerController.getUsableBandwidthPercent(
|
||||||
|
new IqStreamInfo(10e6, 9.9e6, IqDataType.FLOAT32)), 1e-9);
|
||||||
|
assertEquals(0.5, SignalHoundTunerController.getUsableBandwidthPercent(
|
||||||
|
new IqStreamInfo(10e6, 1e6, IqDataType.FLOAT32)), 1e-9);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void invalidSettingsAreAutomatic()
|
||||||
|
{
|
||||||
|
assertEquals(3, SignalHoundTunerController.getValidSetting(3, 3));
|
||||||
|
assertEquals(SignalHoundTunerController.AUTOMATIC, SignalHoundTunerController.getValidSetting(4, 3));
|
||||||
|
assertEquals(SignalHoundTunerController.AUTOMATIC, SignalHoundTunerController.getValidSetting(-5, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void idUsesTheFamilyAndSerialNumber()
|
||||||
|
{
|
||||||
|
assertEquals("Signal Hound SM SER#1234", DiscoveredSignalHoundTuner.getId(SM200));
|
||||||
|
assertEquals("Signal Hound SP145 SER#99", DiscoveredSignalHoundTuner.getId(
|
||||||
|
new DeviceDescriptor(SignalHoundApi.SP, 99, "SP145")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void configurationRoundTrips() throws Exception
|
||||||
|
{
|
||||||
|
SignalHoundTunerConfiguration configuration = new SignalHoundTunerConfiguration("test");
|
||||||
|
configuration.setSampleRate(5_000_000);
|
||||||
|
configuration.setReferenceLevel(-40);
|
||||||
|
configuration.setAttenuation(2);
|
||||||
|
configuration.setGain(1);
|
||||||
|
configuration.setPreselector(true);
|
||||||
|
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
TunerConfiguration restored = mapper.readValue(mapper.writeValueAsString(configuration),
|
||||||
|
TunerConfiguration.class);
|
||||||
|
|
||||||
|
SignalHoundTunerConfiguration signalHound = assertInstanceOf(SignalHoundTunerConfiguration.class, restored);
|
||||||
|
assertEquals(TunerType.SIGNAL_HOUND, signalHound.getTunerType());
|
||||||
|
assertEquals(5_000_000, signalHound.getSampleRate());
|
||||||
|
assertEquals(-40, signalHound.getReferenceLevel());
|
||||||
|
assertEquals(2, signalHound.getAttenuation());
|
||||||
|
assertEquals(1, signalHound.getGain());
|
||||||
|
assertTrue(signalHound.isPreselector());
|
||||||
|
assertEquals("test", signalHound.getUniqueID());
|
||||||
|
assertInstanceOf(SignalHoundTunerConfiguration.class,
|
||||||
|
TunerFactory.getTunerConfiguration(TunerType.SIGNAL_HOUND, "id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void streamsScaledSamplesAndReconfiguresForChanges() throws Exception
|
||||||
|
{
|
||||||
|
FakeDevice device = new FakeDevice(0.01f);
|
||||||
|
ErrorListener errors = new ErrorListener();
|
||||||
|
SignalHoundTunerController controller = new SignalHoundTunerController(SM200, errors, _ -> device);
|
||||||
|
controller.start();
|
||||||
|
|
||||||
|
assertEquals("SM200B", controller.getDeviceInfo().model());
|
||||||
|
assertEquals(20_600_000_000L, controller.getMaximumTunableFrequency());
|
||||||
|
assertEquals(6_250_000, controller.getSignalHoundSampleRate().rate());
|
||||||
|
assertFalse(controller.isPreselectorSupported(), "Only SmDevice and SP145B have a preselector");
|
||||||
|
assertTrue(device.configs.isEmpty(), "The stream isn't configured until there's a listener");
|
||||||
|
|
||||||
|
//Small, since each buffer is up to 512 KB. Buffers are dropped while it is full.
|
||||||
|
BlockingQueue<INativeBuffer> buffers = new ArrayBlockingQueue<>(16);
|
||||||
|
Listener<INativeBuffer> listener = buffers::offer;
|
||||||
|
controller.addBufferListener(listener);
|
||||||
|
|
||||||
|
//0.01 sqrt(mW) at the default -20 dBm reference level is 0.1 of full scale
|
||||||
|
assertEquals(0.1f, firstSample(buffers), 1e-6f);
|
||||||
|
IqConfig first = device.configs.getLast();
|
||||||
|
assertEquals(8, first.decimation());
|
||||||
|
assertEquals(-20.0, first.referenceLevel());
|
||||||
|
assertEquals(IqDataType.FLOAT32, first.dataType());
|
||||||
|
|
||||||
|
controller.setFrequency(460_000_000L);
|
||||||
|
assertEquals(460_000_000.0, device.configs.getLast().centerFrequency());
|
||||||
|
|
||||||
|
controller.setReferenceLevel(-40);
|
||||||
|
assertEquals(-40.0, device.configs.getLast().referenceLevel());
|
||||||
|
//A block read before the change can still be in flight to the listeners
|
||||||
|
awaitSample(buffers, 1.0f);
|
||||||
|
|
||||||
|
controller.setSampleRate(controller.getSampleRates().get(2));
|
||||||
|
assertEquals(4, device.configs.getLast().decimation());
|
||||||
|
assertEquals(12_500_000.0, controller.getSampleRate());
|
||||||
|
awaitSampleCount(buffers, 65536);
|
||||||
|
|
||||||
|
controller.removeBufferListener(listener);
|
||||||
|
assertFalse(device.streaming, "The stream stops without listeners");
|
||||||
|
assertTrue(device.stops > 0);
|
||||||
|
|
||||||
|
controller.stop();
|
||||||
|
assertTrue(device.closed);
|
||||||
|
assertTrue(errors.messages.isEmpty(), "Errors: " + errors.messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void settingsAreAppliedWhenTheStreamStarts() throws Exception
|
||||||
|
{
|
||||||
|
FakeDevice device = new FakeDevice(0.01f);
|
||||||
|
SignalHoundTunerController controller = new SignalHoundTunerController(SM200, new ErrorListener(),
|
||||||
|
_ -> device);
|
||||||
|
controller.start();
|
||||||
|
|
||||||
|
SignalHoundTunerConfiguration configuration = new SignalHoundTunerConfiguration("id");
|
||||||
|
configuration.setFrequency(851_000_000L);
|
||||||
|
configuration.setSampleRate(25_000_000);
|
||||||
|
configuration.setReferenceLevel(-100);
|
||||||
|
configuration.setAttenuation(9);
|
||||||
|
configuration.setGain(2);
|
||||||
|
configuration.setPreselector(true);
|
||||||
|
controller.apply(configuration);
|
||||||
|
|
||||||
|
//Settings that the device doesn't support are corrected in the configuration
|
||||||
|
assertEquals(SignalHoundTunerController.REFERENCE_LEVEL_MINIMUM, configuration.getReferenceLevel());
|
||||||
|
assertEquals(SignalHoundTunerController.AUTOMATIC, configuration.getAttenuation());
|
||||||
|
assertEquals(SignalHoundTunerController.AUTOMATIC, configuration.getGain());
|
||||||
|
assertFalse(configuration.isPreselector());
|
||||||
|
assertTrue(device.configs.isEmpty());
|
||||||
|
|
||||||
|
Listener<INativeBuffer> listener = _ -> {};
|
||||||
|
controller.addBufferListener(listener);
|
||||||
|
IqConfig config = device.configs.getLast();
|
||||||
|
assertEquals(851_000_000.0, config.centerFrequency());
|
||||||
|
assertEquals(2, config.decimation());
|
||||||
|
assertEquals(-60.0, config.referenceLevel());
|
||||||
|
assertEquals(1, device.configs.size(), "Configured once when the stream starts");
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> controller.setGain(1), "The SM doesn't have gain");
|
||||||
|
assertThrows(SourceException.class, () -> controller.setPreselector(true));
|
||||||
|
controller.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readFailureStopsTheTunerWithAnError() throws Exception
|
||||||
|
{
|
||||||
|
FakeDevice device = new FakeDevice(0.01f);
|
||||||
|
ErrorListener errors = new ErrorListener();
|
||||||
|
SignalHoundTunerController controller = new SignalHoundTunerController(SM200, errors, _ -> device);
|
||||||
|
controller.start();
|
||||||
|
controller.addBufferListener(_ -> {});
|
||||||
|
|
||||||
|
device.readFailure = new SignalHoundException(SignalHoundApi.SM, "smGetIQ", -11, "Device disconnected");
|
||||||
|
String message = errors.messages.poll(5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
assertNotNull(message);
|
||||||
|
assertTrue(message.contains("Device disconnected"), message);
|
||||||
|
controller.stop();
|
||||||
|
assertTrue(device.closed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void startFailureClosesTheDevice()
|
||||||
|
{
|
||||||
|
SignalHoundTunerController controller = new SignalHoundTunerController(SM200, new ErrorListener(), _ -> {
|
||||||
|
throw new SignalHoundException(SignalHoundApi.SM, "smOpenDeviceBySerial", -1, "Device not found");
|
||||||
|
});
|
||||||
|
|
||||||
|
SourceException exception = assertThrows(SourceException.class, controller::start);
|
||||||
|
assertTrue(exception.getMessage().contains("Device not found"), exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for a buffer and returns its first I sample.
|
||||||
|
*/
|
||||||
|
private static float firstSample(BlockingQueue<INativeBuffer> buffers) throws InterruptedException
|
||||||
|
{
|
||||||
|
INativeBuffer buffer = buffers.poll(5, TimeUnit.SECONDS);
|
||||||
|
assertNotNull(buffer, "No samples received");
|
||||||
|
return buffer.iteratorInterleaved().next().samples()[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for a buffer whose first I sample is the expected value.
|
||||||
|
*/
|
||||||
|
private static void awaitSample(BlockingQueue<INativeBuffer> buffers, float expected) throws InterruptedException
|
||||||
|
{
|
||||||
|
long end = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
|
||||||
|
float sample = Float.NaN;
|
||||||
|
|
||||||
|
while(System.nanoTime() < end)
|
||||||
|
{
|
||||||
|
sample = firstSample(buffers);
|
||||||
|
|
||||||
|
if(Math.abs(sample - expected) < 1e-5f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(expected, sample, 1e-5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for a buffer with the expected sample count.
|
||||||
|
*/
|
||||||
|
private static void awaitSampleCount(BlockingQueue<INativeBuffer> buffers, int expected) throws InterruptedException
|
||||||
|
{
|
||||||
|
long end = System.nanoTime() + TimeUnit.SECONDS.toNanos(5);
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
while(System.nanoTime() < end)
|
||||||
|
{
|
||||||
|
INativeBuffer buffer = buffers.poll(5, TimeUnit.SECONDS);
|
||||||
|
assertNotNull(buffer, "No samples received");
|
||||||
|
count = buffer.sampleCount();
|
||||||
|
|
||||||
|
if(count == expected)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(expected, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ErrorListener implements ITunerErrorListener
|
||||||
|
{
|
||||||
|
private final BlockingQueue<String> messages = new ArrayBlockingQueue<>(10);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setErrorMessage(String errorMessage)
|
||||||
|
{
|
||||||
|
messages.offer(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tunerRemoved()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulated SM200 that streams a constant sample value, reading as fast as the test reads.
|
||||||
|
*/
|
||||||
|
private static class FakeDevice implements SignalHoundDevice
|
||||||
|
{
|
||||||
|
private final float mValue;
|
||||||
|
private final List<IqConfig> configs = new CopyOnWriteArrayList<>();
|
||||||
|
private volatile boolean streaming;
|
||||||
|
private volatile boolean closed;
|
||||||
|
private volatile int stops;
|
||||||
|
private volatile SignalHoundException readFailure;
|
||||||
|
private volatile RuntimeException firmwareFailure;
|
||||||
|
|
||||||
|
FakeDevice(float value)
|
||||||
|
{
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SignalHoundApi api()
|
||||||
|
{
|
||||||
|
return SignalHoundApi.SM;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int serialNumber()
|
||||||
|
{
|
||||||
|
return 1234;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String model()
|
||||||
|
{
|
||||||
|
return "SM200B";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String firmwareVersion()
|
||||||
|
{
|
||||||
|
if(firmwareFailure != null)
|
||||||
|
{
|
||||||
|
throw firmwareFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "8.1.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Diagnostics diagnostics()
|
||||||
|
{
|
||||||
|
return new Diagnostics(40.0f, 5.0f, 1.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized IqStreamInfo startIq(IqConfig config)
|
||||||
|
{
|
||||||
|
configs.add(config);
|
||||||
|
streaming = true;
|
||||||
|
double rate = SignalHoundFamily.SM.getBaseSampleRate() / (double)config.decimation();
|
||||||
|
return new IqStreamInfo(rate, Math.min(config.bandwidth(), rate * 0.8), config.dataType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized IqBlock readIq(IqBuffer buffer, boolean purge) throws SignalHoundException
|
||||||
|
{
|
||||||
|
if(!streaming)
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Not streaming");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(readFailure != null)
|
||||||
|
{
|
||||||
|
throw readFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.segment().fill((byte)0);
|
||||||
|
|
||||||
|
for(int x = 0; x < buffer.capacity() * 2; x++)
|
||||||
|
{
|
||||||
|
buffer.segment().setAtIndex(ValueLayout.JAVA_FLOAT, x, mValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(1);
|
||||||
|
}
|
||||||
|
catch(InterruptedException e)
|
||||||
|
{
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new IqBlock(buffer.capacity(), System.currentTimeMillis() * 1_000_000L, false, 0, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float iqCorrection()
|
||||||
|
{
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SweepInfo startSweep(SweepConfig config)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sweep readSweep()
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void stop()
|
||||||
|
{
|
||||||
|
streaming = false;
|
||||||
|
stops++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean isClosed()
|
||||||
|
{
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void close()
|
||||||
|
{
|
||||||
|
streaming = false;
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user