mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59e74d70b4 | ||
|
|
8d6b7c3ac8 | ||
|
|
372d2ff870 | ||
|
|
32a8363b45 | ||
|
|
87f1f8986f | ||
|
|
b3c08053fb | ||
|
|
a5208dcf1e | ||
|
|
2782953518 | ||
|
|
366055145e | ||
|
|
39ebdb47b1 | ||
|
|
c788292db8 | ||
|
|
f0dc80437c | ||
|
|
2d6c5c2331 | ||
|
|
bc4e6cb5b0 | ||
|
|
d69ad46c10 | ||
|
|
ba7b616f88 | ||
|
|
8355b7e3b9 | ||
|
|
22ffb656be | ||
|
|
de80d2a6c1 | ||
|
|
a320b4a24f | ||
|
|
fc13777211 | ||
|
|
75347f9d0a | ||
|
|
9abf64c6ab | ||
|
|
1702103f5d | ||
|
|
8f68a989b9 | ||
|
|
b51a0102ef | ||
|
|
e177c2c6b0 | ||
|
|
3567fdf51d | ||
|
|
323097ba65 | ||
|
|
33f2bab7ac | ||
|
|
c063cfb201 | ||
|
|
e99aa3d3b4 | ||
|
|
ec162ff84c | ||
|
|
f0db2865f2 | ||
|
|
f97b2150b9 | ||
|
|
8ce8aaddf9 |
38
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: Bug Report
|
||||||
|
about: Create a report to help us improve sdrtrunk
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**sdrtrunk Version**
|
||||||
|
Indicate which sdrtrunk version you are using. Use 'master-branch' if you are running the latest code.
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Application Log**
|
||||||
|
If applicable, include either the full application log,or a snippet of the error logging.
|
||||||
|
|
||||||
|
**Desktop (optional - complete the following information):**
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
- CPU Cores: [e.g. 4 cores]
|
||||||
|
- RAM:[e.g. 16GB]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: feature
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
16
.github/ISSUE_TEMPLATE/how-to-question-or-does-the-software-support-xxx.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/how-to-question-or-does-the-software-support-xxx.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
name: How-To Question or Does The Software Support xxx
|
||||||
|
about: End user question or support discussion
|
||||||
|
title: STOP - Don't Use Issue Reporting to Post End-User Questions!!!
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
STOP !!!!
|
||||||
|
|
||||||
|
GitHub issues are for reporting software bugs or requesting enhancements.
|
||||||
|
|
||||||
|
If you have a question about how to use the software, or if you want to know if sdrtrunk supports a protocol, tuner, or other aspect, please post your question in one of the Support forums.
|
||||||
|
|
||||||
|
Visit: https://github.com/DSheirer/sdrtrunk/wiki
|
||||||
4
.github/workflows/gradle.yml
vendored
4
.github/workflows/gradle.yml
vendored
@@ -22,10 +22,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 19
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '19'
|
||||||
distribution: 'liberica'
|
distribution: 'liberica'
|
||||||
java-package: 'jdk+fx'
|
java-package: 'jdk+fx'
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
|
|||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -4,7 +4,7 @@
|
|||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<file type="web" url="file://$PROJECT_DIR$" />
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="liberica-19.0.1.fx" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_19_PREVIEW" project-jdk-name="liberica-19.0.1.fx" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
36
artifacts/sdrplay-api-headers/README.txt
Normal file
36
artifacts/sdrplay-api-headers/README.txt
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
Auto-generating Java code from SDRPlay API using jextract.
|
||||||
|
|
||||||
|
Note: the api headers in the 3.11 folder are equivalent to the 3.08 headers. The only
|
||||||
|
difference from the 3.07 headers is the addition of the boolean 'valid' flag to DeviceT structure.
|
||||||
|
|
||||||
|
1. Download JDK19 compatible build of jextract JDK: https://jdk.java.net/jextract/
|
||||||
|
2. If generating on windows platform, modify the sdrplay_api.h to remove the windows
|
||||||
|
pieces of the file. Specifically:
|
||||||
|
2a. Comment out these lines but leave the *HANDLE typedef:
|
||||||
|
|
||||||
|
//#if defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64)
|
||||||
|
//#include "windows.h"
|
||||||
|
//#elif defined (__GNUC__)
|
||||||
|
typedef void *HANDLE;
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
//#ifndef _SDRPLAY_DLL_QUALIFIER
|
||||||
|
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64))
|
||||||
|
//#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
|
||||||
|
//#elif defined(STATIC_LIB) || defined(__GNUC__)
|
||||||
|
//#define _SDRPLAY_DLL_QUALIFIER
|
||||||
|
//#endif
|
||||||
|
//#endif // _SDRPLAY_DLL_QUALIFIER
|
||||||
|
|
||||||
|
2b. Remove the '_SDRPLAY_DLL_QUALIFIER' from in front of the method declarations
|
||||||
|
|
||||||
|
2c. Change the 3.11 version string to 3.08 either in the header file, or in the generated java class
|
||||||
|
|
||||||
|
3. Open command prompt to the root source code where the generated files should be placed:
|
||||||
|
..\SDRtrunk\sdrplay-api\src\main\java
|
||||||
|
|
||||||
|
4. Generate the code:
|
||||||
|
c:\Users\Denny\Downloads\jdk-19-ea2-panama\bin\jextract "C:\Users\Denny\git\sdrtrunk\sdrplay-api\api\v3_11\sdrplay_api.h" -l libsdrplay_api --source
|
||||||
|
|
||||||
|
5. Do this for all versions of the API.
|
||||||
|
|
||||||
229
artifacts/sdrplay-api-headers/v3_07/sdrplay_api.h
Normal file
229
artifacts/sdrplay-api-headers/v3_07/sdrplay_api.h
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
#ifndef SDRPLAY_API_H
|
||||||
|
#define SDRPLAY_API_H
|
||||||
|
|
||||||
|
#include "sdrplay_api_dev.h"
|
||||||
|
#include "sdrplay_api_rx_channel.h"
|
||||||
|
#include "sdrplay_api_callback.h"
|
||||||
|
|
||||||
|
//#if defined(_M_X64) || defined(_M_IX86)
|
||||||
|
//#include "windows.h"
|
||||||
|
//#elif defined (__GNUC__)
|
||||||
|
typedef void *HANDLE;
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
//#ifndef _SDRPLAY_DLL_QUALIFIER
|
||||||
|
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86))
|
||||||
|
//#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
|
||||||
|
//#elif defined(STATIC_LIB) || defined(__GNUC__)
|
||||||
|
//#define _SDRPLAY_DLL_QUALIFIER
|
||||||
|
//#endif
|
||||||
|
//#endif // _SDRPLAY_DLL_QUALIFIER
|
||||||
|
|
||||||
|
// Application code should check that it is compiled against the same API version
|
||||||
|
// sdrplay_api_ApiVersion() returns the API version
|
||||||
|
#define SDRPLAY_API_VERSION (float)(3.07)
|
||||||
|
|
||||||
|
// API Constants
|
||||||
|
#define SDRPLAY_MAX_DEVICES (16)
|
||||||
|
#define SDRPLAY_MAX_TUNERS_PER_DEVICE (2)
|
||||||
|
|
||||||
|
#define SDRPLAY_MAX_SER_NO_LEN (64)
|
||||||
|
#define SDRPLAY_MAX_ROOT_NM_LEN (32)
|
||||||
|
|
||||||
|
#define SDRPLAY_RSP1_ID (1)
|
||||||
|
#define SDRPLAY_RSP1A_ID (255)
|
||||||
|
#define SDRPLAY_RSP2_ID (2)
|
||||||
|
#define SDRPLAY_RSPduo_ID (3)
|
||||||
|
#define SDRPLAY_RSPdx_ID (4)
|
||||||
|
|
||||||
|
// Enum types
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_Success = 0,
|
||||||
|
sdrplay_api_Fail = 1,
|
||||||
|
sdrplay_api_InvalidParam = 2,
|
||||||
|
sdrplay_api_OutOfRange = 3,
|
||||||
|
sdrplay_api_GainUpdateError = 4,
|
||||||
|
sdrplay_api_RfUpdateError = 5,
|
||||||
|
sdrplay_api_FsUpdateError = 6,
|
||||||
|
sdrplay_api_HwError = 7,
|
||||||
|
sdrplay_api_AliasingError = 8,
|
||||||
|
sdrplay_api_AlreadyInitialised = 9,
|
||||||
|
sdrplay_api_NotInitialised = 10,
|
||||||
|
sdrplay_api_NotEnabled = 11,
|
||||||
|
sdrplay_api_HwVerError = 12,
|
||||||
|
sdrplay_api_OutOfMemError = 13,
|
||||||
|
sdrplay_api_ServiceNotResponding = 14,
|
||||||
|
sdrplay_api_StartPending = 15,
|
||||||
|
sdrplay_api_StopPending = 16,
|
||||||
|
sdrplay_api_InvalidMode = 17,
|
||||||
|
sdrplay_api_FailedVerification1 = 18,
|
||||||
|
sdrplay_api_FailedVerification2 = 19,
|
||||||
|
sdrplay_api_FailedVerification3 = 20,
|
||||||
|
sdrplay_api_FailedVerification4 = 21,
|
||||||
|
sdrplay_api_FailedVerification5 = 22,
|
||||||
|
sdrplay_api_FailedVerification6 = 23,
|
||||||
|
sdrplay_api_InvalidServiceVersion = 24
|
||||||
|
} sdrplay_api_ErrT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_Update_None = 0x00000000,
|
||||||
|
|
||||||
|
// Reasons for master only mode
|
||||||
|
sdrplay_api_Update_Dev_Fs = 0x00000001,
|
||||||
|
sdrplay_api_Update_Dev_Ppm = 0x00000002,
|
||||||
|
sdrplay_api_Update_Dev_SyncUpdate = 0x00000004,
|
||||||
|
sdrplay_api_Update_Dev_ResetFlags = 0x00000008,
|
||||||
|
|
||||||
|
sdrplay_api_Update_Rsp1a_BiasTControl = 0x00000010,
|
||||||
|
sdrplay_api_Update_Rsp1a_RfNotchControl = 0x00000020,
|
||||||
|
sdrplay_api_Update_Rsp1a_RfDabNotchControl = 0x00000040,
|
||||||
|
|
||||||
|
sdrplay_api_Update_Rsp2_BiasTControl = 0x00000080,
|
||||||
|
sdrplay_api_Update_Rsp2_AmPortSelect = 0x00000100,
|
||||||
|
sdrplay_api_Update_Rsp2_AntennaControl = 0x00000200,
|
||||||
|
sdrplay_api_Update_Rsp2_RfNotchControl = 0x00000400,
|
||||||
|
sdrplay_api_Update_Rsp2_ExtRefControl = 0x00000800,
|
||||||
|
|
||||||
|
sdrplay_api_Update_RspDuo_ExtRefControl = 0x00001000,
|
||||||
|
|
||||||
|
sdrplay_api_Update_Master_Spare_1 = 0x00002000,
|
||||||
|
sdrplay_api_Update_Master_Spare_2 = 0x00004000,
|
||||||
|
|
||||||
|
// Reasons for master and slave mode
|
||||||
|
// Note: sdrplay_api_Update_Tuner_Gr MUST be the first value defined in this section!
|
||||||
|
sdrplay_api_Update_Tuner_Gr = 0x00008000,
|
||||||
|
sdrplay_api_Update_Tuner_GrLimits = 0x00010000,
|
||||||
|
sdrplay_api_Update_Tuner_Frf = 0x00020000,
|
||||||
|
sdrplay_api_Update_Tuner_BwType = 0x00040000,
|
||||||
|
sdrplay_api_Update_Tuner_IfType = 0x00080000,
|
||||||
|
sdrplay_api_Update_Tuner_DcOffset = 0x00100000,
|
||||||
|
sdrplay_api_Update_Tuner_LoMode = 0x00200000,
|
||||||
|
|
||||||
|
sdrplay_api_Update_Ctrl_DCoffsetIQimbalance = 0x00400000,
|
||||||
|
sdrplay_api_Update_Ctrl_Decimation = 0x00800000,
|
||||||
|
sdrplay_api_Update_Ctrl_Agc = 0x01000000,
|
||||||
|
sdrplay_api_Update_Ctrl_AdsbMode = 0x02000000,
|
||||||
|
sdrplay_api_Update_Ctrl_OverloadMsgAck = 0x04000000,
|
||||||
|
|
||||||
|
sdrplay_api_Update_RspDuo_BiasTControl = 0x08000000,
|
||||||
|
sdrplay_api_Update_RspDuo_AmPortSelect = 0x10000000,
|
||||||
|
sdrplay_api_Update_RspDuo_Tuner1AmNotchControl = 0x20000000,
|
||||||
|
sdrplay_api_Update_RspDuo_RfNotchControl = 0x40000000,
|
||||||
|
sdrplay_api_Update_RspDuo_RfDabNotchControl = 0x80000000,
|
||||||
|
} sdrplay_api_ReasonForUpdateT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_Update_Ext1_None = 0x00000000,
|
||||||
|
|
||||||
|
// Reasons for master only mode
|
||||||
|
sdrplay_api_Update_RspDx_HdrEnable = 0x00000001,
|
||||||
|
sdrplay_api_Update_RspDx_BiasTControl = 0x00000002,
|
||||||
|
sdrplay_api_Update_RspDx_AntennaControl = 0x00000004,
|
||||||
|
sdrplay_api_Update_RspDx_RfNotchControl = 0x00000008,
|
||||||
|
sdrplay_api_Update_RspDx_RfDabNotchControl = 0x00000010,
|
||||||
|
sdrplay_api_Update_RspDx_HdrBw = 0x00000020,
|
||||||
|
|
||||||
|
// Reasons for master and slave mode
|
||||||
|
} sdrplay_api_ReasonForUpdateExtension1T;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_DbgLvl_Disable = 0,
|
||||||
|
sdrplay_api_DbgLvl_Verbose = 1,
|
||||||
|
sdrplay_api_DbgLvl_Warning = 2,
|
||||||
|
sdrplay_api_DbgLvl_Error = 3,
|
||||||
|
sdrplay_api_DbgLvl_Message = 4,
|
||||||
|
} sdrplay_api_DbgLvl_t;
|
||||||
|
|
||||||
|
// Device structure
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char SerNo[SDRPLAY_MAX_SER_NO_LEN];
|
||||||
|
unsigned char hwVer;
|
||||||
|
sdrplay_api_TunerSelectT tuner;
|
||||||
|
sdrplay_api_RspDuoModeT rspDuoMode;
|
||||||
|
double rspDuoSampleFreq;
|
||||||
|
HANDLE dev;
|
||||||
|
} sdrplay_api_DeviceT;
|
||||||
|
|
||||||
|
// Device parameter structure
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_DevParamsT *devParams;
|
||||||
|
sdrplay_api_RxChannelParamsT *rxChannelA;
|
||||||
|
sdrplay_api_RxChannelParamsT *rxChannelB;
|
||||||
|
} sdrplay_api_DeviceParamsT;
|
||||||
|
|
||||||
|
// Extended error message structure
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char file[256];
|
||||||
|
char function[256];
|
||||||
|
int line;
|
||||||
|
char message[1024];
|
||||||
|
} sdrplay_api_ErrorInfoT;
|
||||||
|
|
||||||
|
// Comman API function types
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_Open_t)(void);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_Close_t)(void);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_ApiVersion_t)(float *apiVer);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_LockDeviceApi_t)(void);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_UnlockDeviceApi_t)(void);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_GetDevices_t)(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_SelectDevice_t)(sdrplay_api_DeviceT *device);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_ReleaseDevice_t)(sdrplay_api_DeviceT *device);
|
||||||
|
typedef const char* (*sdrplay_api_GetErrorString_t)(sdrplay_api_ErrT err);
|
||||||
|
typedef sdrplay_api_ErrorInfoT* (*sdrplay_api_GetLastError_t)(sdrplay_api_DeviceT *device);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_DisableHeartbeat_t)(void);
|
||||||
|
|
||||||
|
// Device API function types
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_DebugEnable_t)(HANDLE dev, sdrplay_api_DbgLvl_t dbgLvl);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_GetDeviceParams_t)(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_Init_t)(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_Uninit_t)(HANDLE dev);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_Update_t)(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoActiveTuner_t)(HANDLE dev, sdrplay_api_TunerSelectT *tuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoDualTunerModeSampleRate_t)(double *currentSampleRate);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoMode_t)(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
|
||||||
|
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
|
||||||
|
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||||
|
|
||||||
|
// API function definitions
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Comman API function definitions
|
||||||
|
sdrplay_api_ErrT sdrplay_api_Open(void);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_Close(void);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_ApiVersion(float *apiVer);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_LockDeviceApi(void);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_UnlockDeviceApi(void);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_GetDevices(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_SelectDevice(sdrplay_api_DeviceT *device);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_ReleaseDevice(sdrplay_api_DeviceT *device);
|
||||||
|
const char* sdrplay_api_GetErrorString(sdrplay_api_ErrT err);
|
||||||
|
sdrplay_api_ErrorInfoT* sdrplay_api_GetLastError(sdrplay_api_DeviceT *device);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_DisableHeartbeat(void); // Must be called before sdrplay_api_SelectDevice()
|
||||||
|
|
||||||
|
// Device API function definitions
|
||||||
|
sdrplay_api_ErrT sdrplay_api_DebugEnable(HANDLE dev, sdrplay_api_DbgLvl_t enable);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_GetDeviceParams(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_Init(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_Uninit(HANDLE dev);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_Update(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_SwapRspDuoActiveTuner(HANDLE dev, sdrplay_api_TunerSelectT *currentTuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_SwapRspDuoDualTunerModeSampleRate(HANDLE dev, double *currentSampleRate);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_SwapRspDuoMode(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
|
||||||
|
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
|
||||||
|
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_H
|
||||||
78
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_callback.h
Normal file
78
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_callback.h
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#ifndef SDRPLAY_API_CALLBACK_H
|
||||||
|
#define SDRPLAY_API_CALLBACK_H
|
||||||
|
|
||||||
|
// Event callback enums
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_Overload_Detected = 0,
|
||||||
|
sdrplay_api_Overload_Corrected = 1,
|
||||||
|
} sdrplay_api_PowerOverloadCbEventIdT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_MasterInitialised = 0,
|
||||||
|
sdrplay_api_SlaveAttached = 1,
|
||||||
|
sdrplay_api_SlaveDetached = 2,
|
||||||
|
sdrplay_api_SlaveInitialised = 3,
|
||||||
|
sdrplay_api_SlaveUninitialised = 4,
|
||||||
|
sdrplay_api_MasterDllDisappeared = 5,
|
||||||
|
sdrplay_api_SlaveDllDisappeared = 6,
|
||||||
|
} sdrplay_api_RspDuoModeCbEventIdT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_GainChange = 0,
|
||||||
|
sdrplay_api_PowerOverloadChange = 1,
|
||||||
|
sdrplay_api_DeviceRemoved = 2,
|
||||||
|
sdrplay_api_RspDuoModeChange = 3,
|
||||||
|
} sdrplay_api_EventT;
|
||||||
|
|
||||||
|
// Event callback parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned int gRdB;
|
||||||
|
unsigned int lnaGRdB;
|
||||||
|
double currGain;
|
||||||
|
} sdrplay_api_GainCbParamT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_PowerOverloadCbEventIdT powerOverloadChangeType;
|
||||||
|
} sdrplay_api_PowerOverloadCbParamT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_RspDuoModeCbEventIdT modeChangeType;
|
||||||
|
} sdrplay_api_RspDuoModeCbParamT;
|
||||||
|
|
||||||
|
// Event parameters overlay
|
||||||
|
typedef union
|
||||||
|
{
|
||||||
|
sdrplay_api_GainCbParamT gainParams;
|
||||||
|
sdrplay_api_PowerOverloadCbParamT powerOverloadParams;
|
||||||
|
sdrplay_api_RspDuoModeCbParamT rspDuoModeParams;
|
||||||
|
} sdrplay_api_EventParamsT;
|
||||||
|
|
||||||
|
// Stream callback parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned int firstSampleNum;
|
||||||
|
int grChanged;
|
||||||
|
int rfChanged;
|
||||||
|
int fsChanged;
|
||||||
|
unsigned int numSamples;
|
||||||
|
} sdrplay_api_StreamCbParamsT;
|
||||||
|
|
||||||
|
// Callback function prototypes
|
||||||
|
typedef void (*sdrplay_api_StreamCallback_t)(short *xi, short *xq, sdrplay_api_StreamCbParamsT *params, unsigned int numSamples, unsigned int reset, void *cbContext);
|
||||||
|
typedef void (*sdrplay_api_EventCallback_t)(sdrplay_api_EventT eventId, sdrplay_api_TunerSelectT tuner, sdrplay_api_EventParamsT *params, void *cbContext);
|
||||||
|
|
||||||
|
// Callback function struct
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_StreamCallback_t StreamACbFn;
|
||||||
|
sdrplay_api_StreamCallback_t StreamBCbFn;
|
||||||
|
sdrplay_api_EventCallback_t EventCbFn;
|
||||||
|
} sdrplay_api_CallbackFnsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_CALLBACK_H
|
||||||
55
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_control.h
Normal file
55
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_control.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#ifndef SDRPLAY_API_CONTROL_H
|
||||||
|
#define SDRPLAY_API_CONTROL_H
|
||||||
|
|
||||||
|
// Control parameter enums
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_AGC_DISABLE = 0,
|
||||||
|
sdrplay_api_AGC_100HZ = 1,
|
||||||
|
sdrplay_api_AGC_50HZ = 2,
|
||||||
|
sdrplay_api_AGC_5HZ = 3,
|
||||||
|
sdrplay_api_AGC_CTRL_EN = 4
|
||||||
|
} sdrplay_api_AgcControlT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_ADSB_DECIMATION = 0,
|
||||||
|
sdrplay_api_ADSB_NO_DECIMATION_LOWPASS = 1,
|
||||||
|
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_2MHZ = 2,
|
||||||
|
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_3MHZ = 3
|
||||||
|
} sdrplay_api_AdsbModeT;
|
||||||
|
|
||||||
|
// Control parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char DCenable; // default: 1
|
||||||
|
unsigned char IQenable; // default: 1
|
||||||
|
} sdrplay_api_DcOffsetT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char enable; // default: 0
|
||||||
|
unsigned char decimationFactor; // default: 1
|
||||||
|
unsigned char wideBandSignal; // default: 0
|
||||||
|
} sdrplay_api_DecimationT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_AgcControlT enable; // default: sdrplay_api_AGC_50HZ
|
||||||
|
int setPoint_dBfs; // default: -60
|
||||||
|
unsigned short attack_ms; // default: 0
|
||||||
|
unsigned short decay_ms; // default: 0
|
||||||
|
unsigned short decay_delay_ms; // default: 0
|
||||||
|
unsigned short decay_threshold_dB; // default: 0
|
||||||
|
int syncUpdate; // default: 0
|
||||||
|
} sdrplay_api_AgcT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_DcOffsetT dcOffset;
|
||||||
|
sdrplay_api_DecimationT decimation;
|
||||||
|
sdrplay_api_AgcT agc;
|
||||||
|
sdrplay_api_AdsbModeT adsbMode; //default: sdrplay_api_ADSB_DECIMATION
|
||||||
|
} sdrplay_api_ControlParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_CONTROL_H
|
||||||
51
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_dev.h
Normal file
51
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_dev.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#ifndef SDRPLAY_API_DEV_H
|
||||||
|
#define SDRPLAY_API_DEV_H
|
||||||
|
|
||||||
|
#include "sdrplay_api_rsp1a.h"
|
||||||
|
#include "sdrplay_api_rsp2.h"
|
||||||
|
#include "sdrplay_api_rspDuo.h"
|
||||||
|
#include "sdrplay_api_rspDx.h"
|
||||||
|
|
||||||
|
// Dev parameter enums
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_ISOCH = 0,
|
||||||
|
sdrplay_api_BULK = 1
|
||||||
|
} sdrplay_api_TransferModeT;
|
||||||
|
|
||||||
|
// Dev parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
double fsHz; // default: 2000000.0
|
||||||
|
unsigned char syncUpdate; // default: 0
|
||||||
|
unsigned char reCal; // default: 0
|
||||||
|
} sdrplay_api_FsFreqT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned int sampleNum; // default: 0
|
||||||
|
unsigned int period; // default: 0
|
||||||
|
} sdrplay_api_SyncUpdateT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char resetGainUpdate; // default: 0
|
||||||
|
unsigned char resetRfUpdate; // default: 0
|
||||||
|
unsigned char resetFsUpdate; // default: 0
|
||||||
|
} sdrplay_api_ResetFlagsT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
double ppm; // default: 0.0
|
||||||
|
sdrplay_api_FsFreqT fsFreq;
|
||||||
|
sdrplay_api_SyncUpdateT syncUpdate;
|
||||||
|
sdrplay_api_ResetFlagsT resetFlags;
|
||||||
|
sdrplay_api_TransferModeT mode; // default: sdrplay_api_ISOCH
|
||||||
|
unsigned int samplesPerPkt; // default: 0 (output param)
|
||||||
|
sdrplay_api_Rsp1aParamsT rsp1aParams;
|
||||||
|
sdrplay_api_Rsp2ParamsT rsp2Params;
|
||||||
|
sdrplay_api_RspDuoParamsT rspDuoParams;
|
||||||
|
sdrplay_api_RspDxParamsT rspDxParams;
|
||||||
|
} sdrplay_api_DevParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_DEV_H
|
||||||
22
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rsp1a.h
Normal file
22
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rsp1a.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef SDRPLAY_API_RSP1A_H
|
||||||
|
#define SDRPLAY_API_RSP1A_H
|
||||||
|
|
||||||
|
#define RSPIA_NUM_LNA_STATES 10
|
||||||
|
#define RSPIA_NUM_LNA_STATES_AM 7
|
||||||
|
#define RSPIA_NUM_LNA_STATES_LBAND 9
|
||||||
|
|
||||||
|
// RSP1A parameter enums
|
||||||
|
|
||||||
|
// RSP1A parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char rfNotchEnable; // default: 0
|
||||||
|
unsigned char rfDabNotchEnable; // default: 0
|
||||||
|
} sdrplay_api_Rsp1aParamsT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char biasTEnable; // default: 0
|
||||||
|
} sdrplay_api_Rsp1aTunerParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_RSP1A_H
|
||||||
35
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rsp2.h
Normal file
35
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rsp2.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef SDRPLAY_API_RSP2_H
|
||||||
|
#define SDRPLAY_API_RSP2_H
|
||||||
|
|
||||||
|
#define RSPII_NUM_LNA_STATES 9
|
||||||
|
#define RSPII_NUM_LNA_STATES_AMPORT 5
|
||||||
|
#define RSPII_NUM_LNA_STATES_420MHZ 6
|
||||||
|
|
||||||
|
// RSP2 parameter enums
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_Rsp2_ANTENNA_A = 5,
|
||||||
|
sdrplay_api_Rsp2_ANTENNA_B = 6,
|
||||||
|
} sdrplay_api_Rsp2_AntennaSelectT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_Rsp2_AMPORT_1 = 1,
|
||||||
|
sdrplay_api_Rsp2_AMPORT_2 = 0,
|
||||||
|
} sdrplay_api_Rsp2_AmPortSelectT;
|
||||||
|
|
||||||
|
// RSP2 parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char extRefOutputEn; // default: 0
|
||||||
|
} sdrplay_api_Rsp2ParamsT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char biasTEnable; // default: 0
|
||||||
|
sdrplay_api_Rsp2_AmPortSelectT amPortSel; // default: sdrplay_api_Rsp2_AMPORT_2
|
||||||
|
sdrplay_api_Rsp2_AntennaSelectT antennaSel; // default: sdrplay_api_Rsp2_ANTENNA_A
|
||||||
|
unsigned char rfNotchEnable; // default: 0
|
||||||
|
} sdrplay_api_Rsp2TunerParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_RSP2_H
|
||||||
40
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rspDuo.h
Normal file
40
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rspDuo.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#ifndef SDRPLAY_API_RSPduo_H
|
||||||
|
#define SDRPLAY_API_RSPduo_H
|
||||||
|
|
||||||
|
#define RSPDUO_NUM_LNA_STATES 10
|
||||||
|
#define RSPDUO_NUM_LNA_STATES_AMPORT 5
|
||||||
|
#define RSPDUO_NUM_LNA_STATES_AM 7
|
||||||
|
#define RSPDUO_NUM_LNA_STATES_LBAND 9
|
||||||
|
|
||||||
|
// RSPduo parameter enums
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_RspDuoMode_Unknown = 0,
|
||||||
|
sdrplay_api_RspDuoMode_Single_Tuner = 1,
|
||||||
|
sdrplay_api_RspDuoMode_Dual_Tuner = 2,
|
||||||
|
sdrplay_api_RspDuoMode_Master = 4,
|
||||||
|
sdrplay_api_RspDuoMode_Slave = 8,
|
||||||
|
} sdrplay_api_RspDuoModeT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_RspDuo_AMPORT_1 = 1,
|
||||||
|
sdrplay_api_RspDuo_AMPORT_2 = 0,
|
||||||
|
} sdrplay_api_RspDuo_AmPortSelectT;
|
||||||
|
|
||||||
|
// RSPduo parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int extRefOutputEn; // default: 0
|
||||||
|
} sdrplay_api_RspDuoParamsT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char biasTEnable; // default: 0
|
||||||
|
sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel; // default: sdrplay_api_RspDuo_AMPORT_2
|
||||||
|
unsigned char tuner1AmNotchEnable; // default: 0
|
||||||
|
unsigned char rfNotchEnable; // default: 0
|
||||||
|
unsigned char rfDabNotchEnable; // default: 0
|
||||||
|
} sdrplay_api_RspDuoTunerParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_RSPduo_H
|
||||||
45
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rspDx.h
Normal file
45
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rspDx.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#ifndef SDRPLAY_API_RSPDX_H
|
||||||
|
#define SDRPLAY_API_RSPDX_H
|
||||||
|
|
||||||
|
#include "sdrplay_api_tuner.h"
|
||||||
|
|
||||||
|
#define RSPDX_NUM_LNA_STATES 28 // Number of LNA states in all bands (except where defined differently below)
|
||||||
|
#define RSPDX_NUM_LNA_STATES_AMPORT2_0_12 19 // Number of LNA states when using AM Port 2 between 0 and 12MHz
|
||||||
|
#define RSPDX_NUM_LNA_STATES_AMPORT2_12_60 20 // Number of LNA states when using AM Port 2 between 12 and 60MHz
|
||||||
|
#define RSPDX_NUM_LNA_STATES_VHF_BAND3 27 // Number of LNA states in VHF and Band3
|
||||||
|
#define RSPDX_NUM_LNA_STATES_420MHZ 21 // Number of LNA states in 420MHz band
|
||||||
|
#define RSPDX_NUM_LNA_STATES_LBAND 19 // Number of LNA states in L-band
|
||||||
|
#define RSPDX_NUM_LNA_STATES_DX 26 // Number of LNA states in DX path
|
||||||
|
|
||||||
|
// RSPdx parameter enums
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_RspDx_ANTENNA_A = 0,
|
||||||
|
sdrplay_api_RspDx_ANTENNA_B = 1,
|
||||||
|
sdrplay_api_RspDx_ANTENNA_C = 2,
|
||||||
|
} sdrplay_api_RspDx_AntennaSelectT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_RspDx_HDRMODE_BW_0_200 = 0,
|
||||||
|
sdrplay_api_RspDx_HDRMODE_BW_0_500 = 1,
|
||||||
|
sdrplay_api_RspDx_HDRMODE_BW_1_200 = 2,
|
||||||
|
sdrplay_api_RspDx_HDRMODE_BW_1_700 = 3,
|
||||||
|
} sdrplay_api_RspDx_HdrModeBwT;
|
||||||
|
|
||||||
|
// RSPdx parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char hdrEnable; // default: 0
|
||||||
|
unsigned char biasTEnable; // default: 0
|
||||||
|
sdrplay_api_RspDx_AntennaSelectT antennaSel; // default: sdrplay_api_RspDx_ANTENNA_A
|
||||||
|
unsigned char rfNotchEnable; // default: 0
|
||||||
|
unsigned char rfDabNotchEnable; // default: 0
|
||||||
|
} sdrplay_api_RspDxParamsT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_RspDx_HdrModeBwT hdrBw; // default: sdrplay_api_RspDx_HDRMODE_BW_1_700
|
||||||
|
} sdrplay_api_RspDxTunerParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_RSPDX_H
|
||||||
21
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rx_channel.h
Normal file
21
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_rx_channel.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef SDRPLAY_API_RX_CHANNEL_H
|
||||||
|
#define SDRPLAY_API_RX_CHANNEL_H
|
||||||
|
|
||||||
|
#include "sdrplay_api_tuner.h"
|
||||||
|
#include "sdrplay_api_control.h"
|
||||||
|
#include "sdrplay_api_rsp1a.h"
|
||||||
|
#include "sdrplay_api_rsp2.h"
|
||||||
|
#include "sdrplay_api_rspDuo.h"
|
||||||
|
#include "sdrplay_api_rspDx.h"
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_TunerParamsT tunerParams;
|
||||||
|
sdrplay_api_ControlParamsT ctrlParams;
|
||||||
|
sdrplay_api_Rsp1aTunerParamsT rsp1aTunerParams;
|
||||||
|
sdrplay_api_Rsp2TunerParamsT rsp2TunerParams;
|
||||||
|
sdrplay_api_RspDuoTunerParamsT rspDuoTunerParams;
|
||||||
|
sdrplay_api_RspDxTunerParamsT rspDxTunerParams;
|
||||||
|
} sdrplay_api_RxChannelParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_RX_CHANNEL_H
|
||||||
93
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_tuner.h
Normal file
93
artifacts/sdrplay-api-headers/v3_07/sdrplay_api_tuner.h
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#ifndef SDRPLAY_API_TUNER_H
|
||||||
|
#define SDRPLAY_API_TUNER_H
|
||||||
|
|
||||||
|
#define MAX_BB_GR (59)
|
||||||
|
|
||||||
|
// Tuner parameter enums
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_BW_Undefined = 0,
|
||||||
|
sdrplay_api_BW_0_200 = 200,
|
||||||
|
sdrplay_api_BW_0_300 = 300,
|
||||||
|
sdrplay_api_BW_0_600 = 600,
|
||||||
|
sdrplay_api_BW_1_536 = 1536,
|
||||||
|
sdrplay_api_BW_5_000 = 5000,
|
||||||
|
sdrplay_api_BW_6_000 = 6000,
|
||||||
|
sdrplay_api_BW_7_000 = 7000,
|
||||||
|
sdrplay_api_BW_8_000 = 8000
|
||||||
|
} sdrplay_api_Bw_MHzT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_IF_Undefined = -1,
|
||||||
|
sdrplay_api_IF_Zero = 0,
|
||||||
|
sdrplay_api_IF_0_450 = 450,
|
||||||
|
sdrplay_api_IF_1_620 = 1620,
|
||||||
|
sdrplay_api_IF_2_048 = 2048
|
||||||
|
} sdrplay_api_If_kHzT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_LO_Undefined = 0,
|
||||||
|
sdrplay_api_LO_Auto = 1,
|
||||||
|
sdrplay_api_LO_120MHz = 2,
|
||||||
|
sdrplay_api_LO_144MHz = 3,
|
||||||
|
sdrplay_api_LO_168MHz = 4
|
||||||
|
} sdrplay_api_LoModeT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_EXTENDED_MIN_GR = 0,
|
||||||
|
sdrplay_api_NORMAL_MIN_GR = 20
|
||||||
|
} sdrplay_api_MinGainReductionT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_Tuner_Neither = 0,
|
||||||
|
sdrplay_api_Tuner_A = 1,
|
||||||
|
sdrplay_api_Tuner_B = 2,
|
||||||
|
sdrplay_api_Tuner_Both = 3,
|
||||||
|
} sdrplay_api_TunerSelectT;
|
||||||
|
|
||||||
|
// Tuner parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float curr;
|
||||||
|
float max;
|
||||||
|
float min;
|
||||||
|
} sdrplay_api_GainValuesT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int gRdB; // default: 50
|
||||||
|
unsigned char LNAstate; // default: 0
|
||||||
|
unsigned char syncUpdate; // default: 0
|
||||||
|
sdrplay_api_MinGainReductionT minGr; // default: sdrplay_api_NORMAL_MIN_GR
|
||||||
|
sdrplay_api_GainValuesT gainVals; // output parameter
|
||||||
|
} sdrplay_api_GainT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
double rfHz; // default: 200000000.0
|
||||||
|
unsigned char syncUpdate; // default: 0
|
||||||
|
} sdrplay_api_RfFreqT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char dcCal; // default: 3 (Periodic mode)
|
||||||
|
unsigned char speedUp; // default: 0 (No speedup)
|
||||||
|
int trackTime; // default: 1 (=> time in uSec = (72 * 3 * trackTime) / 24e6 = 9uSec)
|
||||||
|
int refreshRateTime; // default: 2048 (=> time in uSec = (72 * 3 * refreshRateTime) / 24e6 = 18432uSec)
|
||||||
|
} sdrplay_api_DcOffsetTunerT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_Bw_MHzT bwType; // default: sdrplay_api_BW_0_200
|
||||||
|
sdrplay_api_If_kHzT ifType; // default: sdrplay_api_IF_Zero
|
||||||
|
sdrplay_api_LoModeT loMode; // default: sdrplay_api_LO_Auto
|
||||||
|
sdrplay_api_GainT gain;
|
||||||
|
sdrplay_api_RfFreqT rfFreq;
|
||||||
|
sdrplay_api_DcOffsetTunerT dcOffsetTuner;
|
||||||
|
} sdrplay_api_TunerParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_TUNER_H
|
||||||
230
artifacts/sdrplay-api-headers/v3_11/sdrplay_api.h
Normal file
230
artifacts/sdrplay-api-headers/v3_11/sdrplay_api.h
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
#ifndef SDRPLAY_API_H
|
||||||
|
#define SDRPLAY_API_H
|
||||||
|
|
||||||
|
#include "sdrplay_api_dev.h"
|
||||||
|
#include "sdrplay_api_rx_channel.h"
|
||||||
|
#include "sdrplay_api_callback.h"
|
||||||
|
|
||||||
|
//#if defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64)
|
||||||
|
//#include "windows.h"
|
||||||
|
//#elif defined (__GNUC__)
|
||||||
|
typedef void *HANDLE;
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
//#ifndef _SDRPLAY_DLL_QUALIFIER
|
||||||
|
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64))
|
||||||
|
//#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
|
||||||
|
//#elif defined(STATIC_LIB) || defined(__GNUC__)
|
||||||
|
//#define _SDRPLAY_DLL_QUALIFIER
|
||||||
|
//#endif
|
||||||
|
//#endif // _SDRPLAY_DLL_QUALIFIER
|
||||||
|
|
||||||
|
// Application code should check that it is compiled against the same API version
|
||||||
|
// sdrplay_api_ApiVersion() returns the API version
|
||||||
|
#define SDRPLAY_API_VERSION (float)(3.11)
|
||||||
|
|
||||||
|
// API Constants
|
||||||
|
#define SDRPLAY_MAX_DEVICES (16)
|
||||||
|
#define SDRPLAY_MAX_TUNERS_PER_DEVICE (2)
|
||||||
|
|
||||||
|
#define SDRPLAY_MAX_SER_NO_LEN (64)
|
||||||
|
#define SDRPLAY_MAX_ROOT_NM_LEN (32)
|
||||||
|
|
||||||
|
#define SDRPLAY_RSP1_ID (1)
|
||||||
|
#define SDRPLAY_RSP1A_ID (255)
|
||||||
|
#define SDRPLAY_RSP2_ID (2)
|
||||||
|
#define SDRPLAY_RSPduo_ID (3)
|
||||||
|
#define SDRPLAY_RSPdx_ID (4)
|
||||||
|
|
||||||
|
// Enum types
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_Success = 0,
|
||||||
|
sdrplay_api_Fail = 1,
|
||||||
|
sdrplay_api_InvalidParam = 2,
|
||||||
|
sdrplay_api_OutOfRange = 3,
|
||||||
|
sdrplay_api_GainUpdateError = 4,
|
||||||
|
sdrplay_api_RfUpdateError = 5,
|
||||||
|
sdrplay_api_FsUpdateError = 6,
|
||||||
|
sdrplay_api_HwError = 7,
|
||||||
|
sdrplay_api_AliasingError = 8,
|
||||||
|
sdrplay_api_AlreadyInitialised = 9,
|
||||||
|
sdrplay_api_NotInitialised = 10,
|
||||||
|
sdrplay_api_NotEnabled = 11,
|
||||||
|
sdrplay_api_HwVerError = 12,
|
||||||
|
sdrplay_api_OutOfMemError = 13,
|
||||||
|
sdrplay_api_ServiceNotResponding = 14,
|
||||||
|
sdrplay_api_StartPending = 15,
|
||||||
|
sdrplay_api_StopPending = 16,
|
||||||
|
sdrplay_api_InvalidMode = 17,
|
||||||
|
sdrplay_api_FailedVerification1 = 18,
|
||||||
|
sdrplay_api_FailedVerification2 = 19,
|
||||||
|
sdrplay_api_FailedVerification3 = 20,
|
||||||
|
sdrplay_api_FailedVerification4 = 21,
|
||||||
|
sdrplay_api_FailedVerification5 = 22,
|
||||||
|
sdrplay_api_FailedVerification6 = 23,
|
||||||
|
sdrplay_api_InvalidServiceVersion = 24
|
||||||
|
} sdrplay_api_ErrT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_Update_None = 0x00000000,
|
||||||
|
|
||||||
|
// Reasons for master only mode
|
||||||
|
sdrplay_api_Update_Dev_Fs = 0x00000001,
|
||||||
|
sdrplay_api_Update_Dev_Ppm = 0x00000002,
|
||||||
|
sdrplay_api_Update_Dev_SyncUpdate = 0x00000004,
|
||||||
|
sdrplay_api_Update_Dev_ResetFlags = 0x00000008,
|
||||||
|
|
||||||
|
sdrplay_api_Update_Rsp1a_BiasTControl = 0x00000010,
|
||||||
|
sdrplay_api_Update_Rsp1a_RfNotchControl = 0x00000020,
|
||||||
|
sdrplay_api_Update_Rsp1a_RfDabNotchControl = 0x00000040,
|
||||||
|
|
||||||
|
sdrplay_api_Update_Rsp2_BiasTControl = 0x00000080,
|
||||||
|
sdrplay_api_Update_Rsp2_AmPortSelect = 0x00000100,
|
||||||
|
sdrplay_api_Update_Rsp2_AntennaControl = 0x00000200,
|
||||||
|
sdrplay_api_Update_Rsp2_RfNotchControl = 0x00000400,
|
||||||
|
sdrplay_api_Update_Rsp2_ExtRefControl = 0x00000800,
|
||||||
|
|
||||||
|
sdrplay_api_Update_RspDuo_ExtRefControl = 0x00001000,
|
||||||
|
|
||||||
|
sdrplay_api_Update_Master_Spare_1 = 0x00002000,
|
||||||
|
sdrplay_api_Update_Master_Spare_2 = 0x00004000,
|
||||||
|
|
||||||
|
// Reasons for master and slave mode
|
||||||
|
// Note: sdrplay_api_Update_Tuner_Gr MUST be the first value defined in this section!
|
||||||
|
sdrplay_api_Update_Tuner_Gr = 0x00008000,
|
||||||
|
sdrplay_api_Update_Tuner_GrLimits = 0x00010000,
|
||||||
|
sdrplay_api_Update_Tuner_Frf = 0x00020000,
|
||||||
|
sdrplay_api_Update_Tuner_BwType = 0x00040000,
|
||||||
|
sdrplay_api_Update_Tuner_IfType = 0x00080000,
|
||||||
|
sdrplay_api_Update_Tuner_DcOffset = 0x00100000,
|
||||||
|
sdrplay_api_Update_Tuner_LoMode = 0x00200000,
|
||||||
|
|
||||||
|
sdrplay_api_Update_Ctrl_DCoffsetIQimbalance = 0x00400000,
|
||||||
|
sdrplay_api_Update_Ctrl_Decimation = 0x00800000,
|
||||||
|
sdrplay_api_Update_Ctrl_Agc = 0x01000000,
|
||||||
|
sdrplay_api_Update_Ctrl_AdsbMode = 0x02000000,
|
||||||
|
sdrplay_api_Update_Ctrl_OverloadMsgAck = 0x04000000,
|
||||||
|
|
||||||
|
sdrplay_api_Update_RspDuo_BiasTControl = 0x08000000,
|
||||||
|
sdrplay_api_Update_RspDuo_AmPortSelect = 0x10000000,
|
||||||
|
sdrplay_api_Update_RspDuo_Tuner1AmNotchControl = 0x20000000,
|
||||||
|
sdrplay_api_Update_RspDuo_RfNotchControl = 0x40000000,
|
||||||
|
sdrplay_api_Update_RspDuo_RfDabNotchControl = 0x80000000,
|
||||||
|
} sdrplay_api_ReasonForUpdateT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_Update_Ext1_None = 0x00000000,
|
||||||
|
|
||||||
|
// Reasons for master only mode
|
||||||
|
sdrplay_api_Update_RspDx_HdrEnable = 0x00000001,
|
||||||
|
sdrplay_api_Update_RspDx_BiasTControl = 0x00000002,
|
||||||
|
sdrplay_api_Update_RspDx_AntennaControl = 0x00000004,
|
||||||
|
sdrplay_api_Update_RspDx_RfNotchControl = 0x00000008,
|
||||||
|
sdrplay_api_Update_RspDx_RfDabNotchControl = 0x00000010,
|
||||||
|
sdrplay_api_Update_RspDx_HdrBw = 0x00000020,
|
||||||
|
|
||||||
|
// Reasons for master and slave mode
|
||||||
|
} sdrplay_api_ReasonForUpdateExtension1T;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_DbgLvl_Disable = 0,
|
||||||
|
sdrplay_api_DbgLvl_Verbose = 1,
|
||||||
|
sdrplay_api_DbgLvl_Warning = 2,
|
||||||
|
sdrplay_api_DbgLvl_Error = 3,
|
||||||
|
sdrplay_api_DbgLvl_Message = 4,
|
||||||
|
} sdrplay_api_DbgLvl_t;
|
||||||
|
|
||||||
|
// Device structure
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char SerNo[SDRPLAY_MAX_SER_NO_LEN];
|
||||||
|
unsigned char hwVer;
|
||||||
|
sdrplay_api_TunerSelectT tuner;
|
||||||
|
sdrplay_api_RspDuoModeT rspDuoMode;
|
||||||
|
unsigned char valid;
|
||||||
|
double rspDuoSampleFreq;
|
||||||
|
HANDLE dev;
|
||||||
|
} sdrplay_api_DeviceT;
|
||||||
|
|
||||||
|
// Device parameter structure
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_DevParamsT *devParams;
|
||||||
|
sdrplay_api_RxChannelParamsT *rxChannelA;
|
||||||
|
sdrplay_api_RxChannelParamsT *rxChannelB;
|
||||||
|
} sdrplay_api_DeviceParamsT;
|
||||||
|
|
||||||
|
// Extended error message structure
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char file[256];
|
||||||
|
char function[256];
|
||||||
|
int line;
|
||||||
|
char message[1024];
|
||||||
|
} sdrplay_api_ErrorInfoT;
|
||||||
|
|
||||||
|
// Comman API function types
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_Open_t)(void);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_Close_t)(void);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_ApiVersion_t)(float *apiVer);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_LockDeviceApi_t)(void);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_UnlockDeviceApi_t)(void);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_GetDevices_t)(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_SelectDevice_t)(sdrplay_api_DeviceT *device);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_ReleaseDevice_t)(sdrplay_api_DeviceT *device);
|
||||||
|
typedef const char* (*sdrplay_api_GetErrorString_t)(sdrplay_api_ErrT err);
|
||||||
|
typedef sdrplay_api_ErrorInfoT* (*sdrplay_api_GetLastError_t)(sdrplay_api_DeviceT *device);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_DisableHeartbeat_t)(void);
|
||||||
|
|
||||||
|
// Device API function types
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_DebugEnable_t)(HANDLE dev, sdrplay_api_DbgLvl_t dbgLvl);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_GetDeviceParams_t)(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_Init_t)(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_Uninit_t)(HANDLE dev);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_Update_t)(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoActiveTuner_t)(HANDLE dev, sdrplay_api_TunerSelectT *tuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoDualTunerModeSampleRate_t)(double *currentSampleRate, double newSampleRate);
|
||||||
|
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoMode_t)(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
|
||||||
|
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
|
||||||
|
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||||
|
|
||||||
|
// API function definitions
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Comman API function definitions
|
||||||
|
sdrplay_api_ErrT sdrplay_api_Open(void);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_Close(void);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_ApiVersion(float *apiVer);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_LockDeviceApi(void);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_UnlockDeviceApi(void);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_GetDevices(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_SelectDevice(sdrplay_api_DeviceT *device);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_ReleaseDevice(sdrplay_api_DeviceT *device);
|
||||||
|
const char* sdrplay_api_GetErrorString(sdrplay_api_ErrT err);
|
||||||
|
sdrplay_api_ErrorInfoT* sdrplay_api_GetLastError(sdrplay_api_DeviceT *device);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_DisableHeartbeat(void); // Must be called before sdrplay_api_SelectDevice()
|
||||||
|
|
||||||
|
// Device API function definitions
|
||||||
|
sdrplay_api_ErrT sdrplay_api_DebugEnable(HANDLE dev, sdrplay_api_DbgLvl_t enable);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_GetDeviceParams(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_Init(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_Uninit(HANDLE dev);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_Update(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_SwapRspDuoActiveTuner(HANDLE dev, sdrplay_api_TunerSelectT *currentTuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_SwapRspDuoDualTunerModeSampleRate(HANDLE dev, double *currentSampleRate, double newSampleRate);
|
||||||
|
sdrplay_api_ErrT sdrplay_api_SwapRspDuoMode(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
|
||||||
|
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
|
||||||
|
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_H
|
||||||
79
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_callback.h
Normal file
79
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_callback.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#ifndef SDRPLAY_API_CALLBACK_H
|
||||||
|
#define SDRPLAY_API_CALLBACK_H
|
||||||
|
|
||||||
|
// Event callback enums
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_Overload_Detected = 0,
|
||||||
|
sdrplay_api_Overload_Corrected = 1,
|
||||||
|
} sdrplay_api_PowerOverloadCbEventIdT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_MasterInitialised = 0,
|
||||||
|
sdrplay_api_SlaveAttached = 1,
|
||||||
|
sdrplay_api_SlaveDetached = 2,
|
||||||
|
sdrplay_api_SlaveInitialised = 3,
|
||||||
|
sdrplay_api_SlaveUninitialised = 4,
|
||||||
|
sdrplay_api_MasterDllDisappeared = 5,
|
||||||
|
sdrplay_api_SlaveDllDisappeared = 6,
|
||||||
|
} sdrplay_api_RspDuoModeCbEventIdT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_GainChange = 0,
|
||||||
|
sdrplay_api_PowerOverloadChange = 1,
|
||||||
|
sdrplay_api_DeviceRemoved = 2,
|
||||||
|
sdrplay_api_RspDuoModeChange = 3,
|
||||||
|
sdrplay_api_DeviceFailure = 4,
|
||||||
|
} sdrplay_api_EventT;
|
||||||
|
|
||||||
|
// Event callback parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned int gRdB;
|
||||||
|
unsigned int lnaGRdB;
|
||||||
|
double currGain;
|
||||||
|
} sdrplay_api_GainCbParamT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_PowerOverloadCbEventIdT powerOverloadChangeType;
|
||||||
|
} sdrplay_api_PowerOverloadCbParamT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_RspDuoModeCbEventIdT modeChangeType;
|
||||||
|
} sdrplay_api_RspDuoModeCbParamT;
|
||||||
|
|
||||||
|
// Event parameters overlay
|
||||||
|
typedef union
|
||||||
|
{
|
||||||
|
sdrplay_api_GainCbParamT gainParams;
|
||||||
|
sdrplay_api_PowerOverloadCbParamT powerOverloadParams;
|
||||||
|
sdrplay_api_RspDuoModeCbParamT rspDuoModeParams;
|
||||||
|
} sdrplay_api_EventParamsT;
|
||||||
|
|
||||||
|
// Stream callback parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned int firstSampleNum;
|
||||||
|
int grChanged;
|
||||||
|
int rfChanged;
|
||||||
|
int fsChanged;
|
||||||
|
unsigned int numSamples;
|
||||||
|
} sdrplay_api_StreamCbParamsT;
|
||||||
|
|
||||||
|
// Callback function prototypes
|
||||||
|
typedef void (*sdrplay_api_StreamCallback_t)(short *xi, short *xq, sdrplay_api_StreamCbParamsT *params, unsigned int numSamples, unsigned int reset, void *cbContext);
|
||||||
|
typedef void (*sdrplay_api_EventCallback_t)(sdrplay_api_EventT eventId, sdrplay_api_TunerSelectT tuner, sdrplay_api_EventParamsT *params, void *cbContext);
|
||||||
|
|
||||||
|
// Callback function struct
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_StreamCallback_t StreamACbFn;
|
||||||
|
sdrplay_api_StreamCallback_t StreamBCbFn;
|
||||||
|
sdrplay_api_EventCallback_t EventCbFn;
|
||||||
|
} sdrplay_api_CallbackFnsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_CALLBACK_H
|
||||||
55
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_control.h
Normal file
55
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_control.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#ifndef SDRPLAY_API_CONTROL_H
|
||||||
|
#define SDRPLAY_API_CONTROL_H
|
||||||
|
|
||||||
|
// Control parameter enums
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_AGC_DISABLE = 0,
|
||||||
|
sdrplay_api_AGC_100HZ = 1,
|
||||||
|
sdrplay_api_AGC_50HZ = 2,
|
||||||
|
sdrplay_api_AGC_5HZ = 3,
|
||||||
|
sdrplay_api_AGC_CTRL_EN = 4
|
||||||
|
} sdrplay_api_AgcControlT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_ADSB_DECIMATION = 0,
|
||||||
|
sdrplay_api_ADSB_NO_DECIMATION_LOWPASS = 1,
|
||||||
|
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_2MHZ = 2,
|
||||||
|
sdrplay_api_ADSB_NO_DECIMATION_BANDPASS_3MHZ = 3
|
||||||
|
} sdrplay_api_AdsbModeT;
|
||||||
|
|
||||||
|
// Control parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char DCenable; // default: 1
|
||||||
|
unsigned char IQenable; // default: 1
|
||||||
|
} sdrplay_api_DcOffsetT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char enable; // default: 0
|
||||||
|
unsigned char decimationFactor; // default: 1
|
||||||
|
unsigned char wideBandSignal; // default: 0
|
||||||
|
} sdrplay_api_DecimationT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_AgcControlT enable; // default: sdrplay_api_AGC_50HZ
|
||||||
|
int setPoint_dBfs; // default: -60
|
||||||
|
unsigned short attack_ms; // default: 0
|
||||||
|
unsigned short decay_ms; // default: 0
|
||||||
|
unsigned short decay_delay_ms; // default: 0
|
||||||
|
unsigned short decay_threshold_dB; // default: 0
|
||||||
|
int syncUpdate; // default: 0
|
||||||
|
} sdrplay_api_AgcT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_DcOffsetT dcOffset;
|
||||||
|
sdrplay_api_DecimationT decimation;
|
||||||
|
sdrplay_api_AgcT agc;
|
||||||
|
sdrplay_api_AdsbModeT adsbMode; //default: sdrplay_api_ADSB_DECIMATION
|
||||||
|
} sdrplay_api_ControlParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_CONTROL_H
|
||||||
51
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_dev.h
Normal file
51
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_dev.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#ifndef SDRPLAY_API_DEV_H
|
||||||
|
#define SDRPLAY_API_DEV_H
|
||||||
|
|
||||||
|
#include "sdrplay_api_rsp1a.h"
|
||||||
|
#include "sdrplay_api_rsp2.h"
|
||||||
|
#include "sdrplay_api_rspDuo.h"
|
||||||
|
#include "sdrplay_api_rspDx.h"
|
||||||
|
|
||||||
|
// Dev parameter enums
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_ISOCH = 0,
|
||||||
|
sdrplay_api_BULK = 1
|
||||||
|
} sdrplay_api_TransferModeT;
|
||||||
|
|
||||||
|
// Dev parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
double fsHz; // default: 2000000.0
|
||||||
|
unsigned char syncUpdate; // default: 0
|
||||||
|
unsigned char reCal; // default: 0
|
||||||
|
} sdrplay_api_FsFreqT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned int sampleNum; // default: 0
|
||||||
|
unsigned int period; // default: 0
|
||||||
|
} sdrplay_api_SyncUpdateT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char resetGainUpdate; // default: 0
|
||||||
|
unsigned char resetRfUpdate; // default: 0
|
||||||
|
unsigned char resetFsUpdate; // default: 0
|
||||||
|
} sdrplay_api_ResetFlagsT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
double ppm; // default: 0.0
|
||||||
|
sdrplay_api_FsFreqT fsFreq;
|
||||||
|
sdrplay_api_SyncUpdateT syncUpdate;
|
||||||
|
sdrplay_api_ResetFlagsT resetFlags;
|
||||||
|
sdrplay_api_TransferModeT mode; // default: sdrplay_api_ISOCH
|
||||||
|
unsigned int samplesPerPkt; // default: 0 (output param)
|
||||||
|
sdrplay_api_Rsp1aParamsT rsp1aParams;
|
||||||
|
sdrplay_api_Rsp2ParamsT rsp2Params;
|
||||||
|
sdrplay_api_RspDuoParamsT rspDuoParams;
|
||||||
|
sdrplay_api_RspDxParamsT rspDxParams;
|
||||||
|
} sdrplay_api_DevParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_DEV_H
|
||||||
22
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rsp1a.h
Normal file
22
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rsp1a.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef SDRPLAY_API_RSP1A_H
|
||||||
|
#define SDRPLAY_API_RSP1A_H
|
||||||
|
|
||||||
|
#define RSPIA_NUM_LNA_STATES 10
|
||||||
|
#define RSPIA_NUM_LNA_STATES_AM 7
|
||||||
|
#define RSPIA_NUM_LNA_STATES_LBAND 9
|
||||||
|
|
||||||
|
// RSP1A parameter enums
|
||||||
|
|
||||||
|
// RSP1A parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char rfNotchEnable; // default: 0
|
||||||
|
unsigned char rfDabNotchEnable; // default: 0
|
||||||
|
} sdrplay_api_Rsp1aParamsT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char biasTEnable; // default: 0
|
||||||
|
} sdrplay_api_Rsp1aTunerParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_RSP1A_H
|
||||||
35
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rsp2.h
Normal file
35
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rsp2.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef SDRPLAY_API_RSP2_H
|
||||||
|
#define SDRPLAY_API_RSP2_H
|
||||||
|
|
||||||
|
#define RSPII_NUM_LNA_STATES 9
|
||||||
|
#define RSPII_NUM_LNA_STATES_AMPORT 5
|
||||||
|
#define RSPII_NUM_LNA_STATES_420MHZ 6
|
||||||
|
|
||||||
|
// RSP2 parameter enums
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_Rsp2_ANTENNA_A = 5,
|
||||||
|
sdrplay_api_Rsp2_ANTENNA_B = 6,
|
||||||
|
} sdrplay_api_Rsp2_AntennaSelectT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_Rsp2_AMPORT_1 = 1,
|
||||||
|
sdrplay_api_Rsp2_AMPORT_2 = 0,
|
||||||
|
} sdrplay_api_Rsp2_AmPortSelectT;
|
||||||
|
|
||||||
|
// RSP2 parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char extRefOutputEn; // default: 0
|
||||||
|
} sdrplay_api_Rsp2ParamsT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char biasTEnable; // default: 0
|
||||||
|
sdrplay_api_Rsp2_AmPortSelectT amPortSel; // default: sdrplay_api_Rsp2_AMPORT_2
|
||||||
|
sdrplay_api_Rsp2_AntennaSelectT antennaSel; // default: sdrplay_api_Rsp2_ANTENNA_A
|
||||||
|
unsigned char rfNotchEnable; // default: 0
|
||||||
|
} sdrplay_api_Rsp2TunerParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_RSP2_H
|
||||||
40
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rspDuo.h
Normal file
40
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rspDuo.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#ifndef SDRPLAY_API_RSPduo_H
|
||||||
|
#define SDRPLAY_API_RSPduo_H
|
||||||
|
|
||||||
|
#define RSPDUO_NUM_LNA_STATES 10
|
||||||
|
#define RSPDUO_NUM_LNA_STATES_AMPORT 5
|
||||||
|
#define RSPDUO_NUM_LNA_STATES_AM 7
|
||||||
|
#define RSPDUO_NUM_LNA_STATES_LBAND 9
|
||||||
|
|
||||||
|
// RSPduo parameter enums
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_RspDuoMode_Unknown = 0,
|
||||||
|
sdrplay_api_RspDuoMode_Single_Tuner = 1,
|
||||||
|
sdrplay_api_RspDuoMode_Dual_Tuner = 2,
|
||||||
|
sdrplay_api_RspDuoMode_Master = 4,
|
||||||
|
sdrplay_api_RspDuoMode_Slave = 8,
|
||||||
|
} sdrplay_api_RspDuoModeT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_RspDuo_AMPORT_1 = 1,
|
||||||
|
sdrplay_api_RspDuo_AMPORT_2 = 0,
|
||||||
|
} sdrplay_api_RspDuo_AmPortSelectT;
|
||||||
|
|
||||||
|
// RSPduo parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int extRefOutputEn; // default: 0
|
||||||
|
} sdrplay_api_RspDuoParamsT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char biasTEnable; // default: 0
|
||||||
|
sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel; // default: sdrplay_api_RspDuo_AMPORT_2
|
||||||
|
unsigned char tuner1AmNotchEnable; // default: 0
|
||||||
|
unsigned char rfNotchEnable; // default: 0
|
||||||
|
unsigned char rfDabNotchEnable; // default: 0
|
||||||
|
} sdrplay_api_RspDuoTunerParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_RSPduo_H
|
||||||
46
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rspDx.h
Normal file
46
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rspDx.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#ifndef SDRPLAY_API_RSPDX_H
|
||||||
|
#define SDRPLAY_API_RSPDX_H
|
||||||
|
|
||||||
|
#include "sdrplay_api_tuner.h"
|
||||||
|
|
||||||
|
#define RSPDX_NUM_LNA_STATES 28 // Number of LNA states in all bands (except where defined differently below)
|
||||||
|
#define RSPDX_NUM_LNA_STATES_AMPORT2_0_12 19 // Number of LNA states when using AM Port 2 between 0 and 12MHz
|
||||||
|
#define RSPDX_NUM_LNA_STATES_AMPORT2_12_50 20 // Number of LNA states when using AM Port 2 between 12 and 50MHz
|
||||||
|
#define RSPDX_NUM_LNA_STATES_AMPORT2_50_60 25 // Number of LNA states when using AM Port 2 between 50 and 60MHz
|
||||||
|
#define RSPDX_NUM_LNA_STATES_VHF_BAND3 27 // Number of LNA states in VHF and Band3
|
||||||
|
#define RSPDX_NUM_LNA_STATES_420MHZ 21 // Number of LNA states in 420MHz band
|
||||||
|
#define RSPDX_NUM_LNA_STATES_LBAND 19 // Number of LNA states in L-band
|
||||||
|
#define RSPDX_NUM_LNA_STATES_DX 22 // Number of LNA states in DX path
|
||||||
|
|
||||||
|
// RSPdx parameter enums
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_RspDx_ANTENNA_A = 0,
|
||||||
|
sdrplay_api_RspDx_ANTENNA_B = 1,
|
||||||
|
sdrplay_api_RspDx_ANTENNA_C = 2,
|
||||||
|
} sdrplay_api_RspDx_AntennaSelectT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_RspDx_HDRMODE_BW_0_200 = 0,
|
||||||
|
sdrplay_api_RspDx_HDRMODE_BW_0_500 = 1,
|
||||||
|
sdrplay_api_RspDx_HDRMODE_BW_1_200 = 2,
|
||||||
|
sdrplay_api_RspDx_HDRMODE_BW_1_700 = 3,
|
||||||
|
} sdrplay_api_RspDx_HdrModeBwT;
|
||||||
|
|
||||||
|
// RSPdx parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char hdrEnable; // default: 0
|
||||||
|
unsigned char biasTEnable; // default: 0
|
||||||
|
sdrplay_api_RspDx_AntennaSelectT antennaSel; // default: sdrplay_api_RspDx_ANTENNA_A
|
||||||
|
unsigned char rfNotchEnable; // default: 0
|
||||||
|
unsigned char rfDabNotchEnable; // default: 0
|
||||||
|
} sdrplay_api_RspDxParamsT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_RspDx_HdrModeBwT hdrBw; // default: sdrplay_api_RspDx_HDRMODE_BW_1_700
|
||||||
|
} sdrplay_api_RspDxTunerParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_RSPDX_H
|
||||||
21
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rx_channel.h
Normal file
21
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_rx_channel.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef SDRPLAY_API_RX_CHANNEL_H
|
||||||
|
#define SDRPLAY_API_RX_CHANNEL_H
|
||||||
|
|
||||||
|
#include "sdrplay_api_tuner.h"
|
||||||
|
#include "sdrplay_api_control.h"
|
||||||
|
#include "sdrplay_api_rsp1a.h"
|
||||||
|
#include "sdrplay_api_rsp2.h"
|
||||||
|
#include "sdrplay_api_rspDuo.h"
|
||||||
|
#include "sdrplay_api_rspDx.h"
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_TunerParamsT tunerParams;
|
||||||
|
sdrplay_api_ControlParamsT ctrlParams;
|
||||||
|
sdrplay_api_Rsp1aTunerParamsT rsp1aTunerParams;
|
||||||
|
sdrplay_api_Rsp2TunerParamsT rsp2TunerParams;
|
||||||
|
sdrplay_api_RspDuoTunerParamsT rspDuoTunerParams;
|
||||||
|
sdrplay_api_RspDxTunerParamsT rspDxTunerParams;
|
||||||
|
} sdrplay_api_RxChannelParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_RX_CHANNEL_H
|
||||||
93
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_tuner.h
Normal file
93
artifacts/sdrplay-api-headers/v3_11/sdrplay_api_tuner.h
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#ifndef SDRPLAY_API_TUNER_H
|
||||||
|
#define SDRPLAY_API_TUNER_H
|
||||||
|
|
||||||
|
#define MAX_BB_GR (59)
|
||||||
|
|
||||||
|
// Tuner parameter enums
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_BW_Undefined = 0,
|
||||||
|
sdrplay_api_BW_0_200 = 200,
|
||||||
|
sdrplay_api_BW_0_300 = 300,
|
||||||
|
sdrplay_api_BW_0_600 = 600,
|
||||||
|
sdrplay_api_BW_1_536 = 1536,
|
||||||
|
sdrplay_api_BW_5_000 = 5000,
|
||||||
|
sdrplay_api_BW_6_000 = 6000,
|
||||||
|
sdrplay_api_BW_7_000 = 7000,
|
||||||
|
sdrplay_api_BW_8_000 = 8000
|
||||||
|
} sdrplay_api_Bw_MHzT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_IF_Undefined = -1,
|
||||||
|
sdrplay_api_IF_Zero = 0,
|
||||||
|
sdrplay_api_IF_0_450 = 450,
|
||||||
|
sdrplay_api_IF_1_620 = 1620,
|
||||||
|
sdrplay_api_IF_2_048 = 2048
|
||||||
|
} sdrplay_api_If_kHzT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_LO_Undefined = 0,
|
||||||
|
sdrplay_api_LO_Auto = 1,
|
||||||
|
sdrplay_api_LO_120MHz = 2,
|
||||||
|
sdrplay_api_LO_144MHz = 3,
|
||||||
|
sdrplay_api_LO_168MHz = 4
|
||||||
|
} sdrplay_api_LoModeT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_EXTENDED_MIN_GR = 0,
|
||||||
|
sdrplay_api_NORMAL_MIN_GR = 20
|
||||||
|
} sdrplay_api_MinGainReductionT;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
sdrplay_api_Tuner_Neither = 0,
|
||||||
|
sdrplay_api_Tuner_A = 1,
|
||||||
|
sdrplay_api_Tuner_B = 2,
|
||||||
|
sdrplay_api_Tuner_Both = 3,
|
||||||
|
} sdrplay_api_TunerSelectT;
|
||||||
|
|
||||||
|
// Tuner parameter structs
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float curr;
|
||||||
|
float max;
|
||||||
|
float min;
|
||||||
|
} sdrplay_api_GainValuesT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int gRdB; // default: 50
|
||||||
|
unsigned char LNAstate; // default: 0
|
||||||
|
unsigned char syncUpdate; // default: 0
|
||||||
|
sdrplay_api_MinGainReductionT minGr; // default: sdrplay_api_NORMAL_MIN_GR
|
||||||
|
sdrplay_api_GainValuesT gainVals; // output parameter
|
||||||
|
} sdrplay_api_GainT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
double rfHz; // default: 200000000.0
|
||||||
|
unsigned char syncUpdate; // default: 0
|
||||||
|
} sdrplay_api_RfFreqT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char dcCal; // default: 3 (Periodic mode)
|
||||||
|
unsigned char speedUp; // default: 0 (No speedup)
|
||||||
|
int trackTime; // default: 1 (=> time in uSec = (72 * 3 * trackTime) / 24e6 = 9uSec)
|
||||||
|
int refreshRateTime; // default: 2048 (=> time in uSec = (72 * 3 * refreshRateTime) / 24e6 = 18432uSec)
|
||||||
|
} sdrplay_api_DcOffsetTunerT;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
sdrplay_api_Bw_MHzT bwType; // default: sdrplay_api_BW_0_200
|
||||||
|
sdrplay_api_If_kHzT ifType; // default: sdrplay_api_IF_Zero
|
||||||
|
sdrplay_api_LoModeT loMode; // default: sdrplay_api_LO_Auto
|
||||||
|
sdrplay_api_GainT gain;
|
||||||
|
sdrplay_api_RfFreqT rfFreq;
|
||||||
|
sdrplay_api_DcOffsetTunerT dcOffsetTuner;
|
||||||
|
} sdrplay_api_TunerParamsT;
|
||||||
|
|
||||||
|
#endif //SDRPLAY_API_TUNER_H
|
||||||
280
build.gradle
280
build.gradle
@@ -1,6 +1,8 @@
|
|||||||
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,6 +18,29 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* ****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructions for building/compiling the sdrtrunk application.
|
||||||
|
*
|
||||||
|
* Prerequisites:
|
||||||
|
* Install and configure an OpenJDK version 19+ that includes the JavaFX modules (e.g. Bellsoft Liberica JDK)
|
||||||
|
* - Optional: install and configure Gradle 7.6+
|
||||||
|
*
|
||||||
|
* Scenario 1: run the application via gradle command line from the source code root directory:
|
||||||
|
* command: ./gradlew run
|
||||||
|
*
|
||||||
|
* Scenario 2: create an application release for the current operating system
|
||||||
|
* command: ./gradlew runtimeZipCurrent
|
||||||
|
* Note: release image is located in the /build/image/ directory
|
||||||
|
*
|
||||||
|
* Scenario 3: create releases for Linux and OSX operating systems using downloaded JDKs
|
||||||
|
* command: ./gradlew runtimeZipOthers
|
||||||
|
* Note: release images are located in the /build/image/ directory
|
||||||
|
*
|
||||||
|
* Scenario 4: create release for Windows operating system using downloaded JDK
|
||||||
|
* command: ./gradlew runtimeZipWindows
|
||||||
|
* Note: release image is located in the /build/image/ directory
|
||||||
|
*/
|
||||||
plugins {
|
plugins {
|
||||||
id 'application'
|
id 'application'
|
||||||
id 'java'
|
id 'java'
|
||||||
@@ -28,8 +53,14 @@ repositories {
|
|||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
|
|
||||||
version = '0.5.0'
|
version = '0.6.0-alpha5'
|
||||||
sourceCompatibility = '17'
|
|
||||||
|
//Java 19 is required for this version of the Project Panama preview/incubator feature
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(19)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main.java.srcDirs 'src/main'
|
main.java.srcDirs 'src/main'
|
||||||
@@ -90,19 +121,46 @@ dependencies {
|
|||||||
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is needed for the JDK17 vector API ... until it moves out of incubation
|
* This is needed for the JDK19 panama and vector API ... until it moves out of incubation or preview
|
||||||
*/
|
*/
|
||||||
tasks.withType(JavaCompile) {
|
tasks.withType(JavaCompile) {
|
||||||
|
options.compilerArgs.add("--enable-preview") //Panama Foreign Function/Memory is preview in JDK 19
|
||||||
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Needed while Panama Vector API remains in incubator
|
options.compilerArgs.add("--add-modules=jdk.incubator.vector") //Needed while Panama Vector API remains in incubator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JVM Arguments for running the application on Windows.
|
||||||
|
*/
|
||||||
|
def jvmArgsWindows =
|
||||||
|
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
|
||||||
|
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED', //windows users for JIDE
|
||||||
|
'--add-modules=jdk.incubator.vector', //Needed while Project Panama API is still in incubator
|
||||||
|
'--enable-preview', //Project Panama Vector & Foreign Function APIs remain in incubator
|
||||||
|
'--enable-native-access=ALL-UNNAMED',
|
||||||
|
'-Djava.library.path=c:\\Program Files\\SDRplay\\API\\x64'] //Allows Project Panama Foreign Function access to system library
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JVM Arguments for running the application on Linux.
|
||||||
|
*/
|
||||||
|
def jvmArgsLinux =
|
||||||
|
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
|
||||||
|
'--add-modules=jdk.incubator.vector', //Needed while Project Panama API is still in incubator
|
||||||
|
'--enable-preview', //Project Panama Vector & Foreign Function APIs remain in incubator
|
||||||
|
'--enable-native-access=ALL-UNNAMED',
|
||||||
|
'-Djava.library.path=/usr/local/lib'] //Allows Project Panama Foreign Function access to system library
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
|
mainClassName = "io.github.dsheirer.gui.SDRTrunk"
|
||||||
applicationDefaultJvmArgs =
|
|
||||||
['--add-exports=javafx.base/com.sun.javafx.event=ALL-UNNAMED', //Needed for controls-fx.jar
|
if(os.isWindows()) {
|
||||||
'--add-modules=jdk.incubator.vector', //Needed while Panama Vector API remains in incubator
|
applicationDefaultJvmArgs = jvmArgsWindows
|
||||||
'--add-exports=java.desktop/com.sun.java.swing.plaf.windows=ALL-UNNAMED'] //Windows Swing - jide-oss library
|
}
|
||||||
|
else {
|
||||||
|
applicationDefaultJvmArgs = jvmArgsLinux
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
@@ -111,7 +169,7 @@ jar {
|
|||||||
'Implementation-Title' : 'sdrtrunk project',
|
'Implementation-Title' : 'sdrtrunk project',
|
||||||
'Implementation-Version' : archiveVersion,
|
'Implementation-Version' : archiveVersion,
|
||||||
'Created-By' : "Gradle ${gradle.gradleVersion}",
|
'Created-By' : "Gradle ${gradle.gradleVersion}",
|
||||||
'Build-Timestamp' : new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
|
'Build-Timestamp' : new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
|
||||||
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
|
'Build-JDK' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']}",
|
||||||
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
|
'Build-OS' : "${System.properties['os.name']} (${System.properties['os.arch']} ${System.properties['os.version']}"
|
||||||
)
|
)
|
||||||
@@ -145,82 +203,152 @@ String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
|
|||||||
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.0.1-full.jdk'
|
String jdk_osx_aarch64 = jdk_base + 'osx-arm64/jdk-19.0.1-full.jdk'
|
||||||
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-19.0.1-full'
|
String jdk_windows_x86_64 = jdk_base + 'windows-x64/jdk-19.0.1-full'
|
||||||
|
|
||||||
boolean hasTargetJdks = file(jdk_linux_x86_64).exists() || file(jdk_linux_aarch64).exists() ||
|
/**
|
||||||
file(jdk_osx_x86_64).exists() || file(jdk_osx_aarch64).exists() || file(jdk_windows_x86_64).exists()
|
* Download URLs to download the JDK as part of the gradle build packaging process
|
||||||
|
*/
|
||||||
|
def jdk_download_base = "https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-"
|
||||||
|
def jdk_download_suffix = "-full.tar.gz"
|
||||||
|
def jdk_download_linux_aarch64 = jdk_download_base + "linux-aarch64" + jdk_download_suffix
|
||||||
|
def jdk_download_linux_x86_64 = jdk_download_base + "linux-amd64" + jdk_download_suffix
|
||||||
|
def jdk_download_osx_x86_64 = jdk_download_base + "macos-aarch64" + jdk_download_suffix
|
||||||
|
def jdk_download_osx_aarch64 = jdk_download_base + "macos-amd64" + jdk_download_suffix
|
||||||
|
def jdk_download_windows_x86_64 = jdk_download_base + "windows-amd64-full.zip"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto-download target JDKs. Defaults to false, or the user can override via the gradle command line by
|
* Configures the runtime zip task with additional options/settings.
|
||||||
* including this option: '-PdownloadJdks=true'
|
* @param rt task to configure
|
||||||
|
* @param jvmArgs to use (windows or linux)
|
||||||
|
* @return configured task.
|
||||||
*/
|
*/
|
||||||
boolean downloadJdks = project.hasProperty('downloadJdks') ? project.getProperty('downloadJdks') : false
|
def configure(org.beryx.runtime.RuntimeZipTask rt, List<String> jvmArgs) {
|
||||||
|
//Use the correct set of JVM arguments
|
||||||
/**
|
rt.extension.launcherData.get().jvmArgs = jvmArgs
|
||||||
* Beryx Runtime plugin for creating OS and CPU targeted build(s) of the software.
|
|
||||||
*
|
|
||||||
* Option 1: Create a single target build using the locally installed JDK, or optionally create target builds for
|
|
||||||
* all OS and CPU targets when pre-downloaded target JDKs are available (see 'jdk_*' properties above).
|
|
||||||
*
|
|
||||||
* >: gradle runtimeZip
|
|
||||||
*
|
|
||||||
* Option 2: Create all OS and CPU target builds by automatically downloading all JDKs needed to build the target images
|
|
||||||
*
|
|
||||||
* >: gradle runtimeZip -PdownloadJdks=true
|
|
||||||
*/
|
|
||||||
runtime {
|
|
||||||
//Build by automatically downloading JDKs for the OS and CPU targets
|
|
||||||
if(downloadJdks) {
|
|
||||||
println("Building all runtime images")
|
|
||||||
println("The [ > jre: ] build phase downloads several target JDKs and this may take a while ... please wait")
|
|
||||||
targetPlatform(targetLinuxAarch64) {
|
|
||||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-aarch64-full.tar.gz")
|
|
||||||
}
|
|
||||||
targetPlatform(targetLinuxX86_64) {
|
|
||||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-linux-amd64-full.tar.gz")
|
|
||||||
}
|
|
||||||
targetPlatform(targetOsxAarch64) {
|
|
||||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-aarch64-full.tar.gz")
|
|
||||||
}
|
|
||||||
targetPlatform(targetOsxX86_64) {
|
|
||||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-macos-amd64-full.tar.gz")
|
|
||||||
}
|
|
||||||
targetPlatform(targetWindowsX86_64) {
|
|
||||||
jdkHome = jdkDownload("https://download.bell-sw.com/java/19.0.1+11/bellsoft-jdk19.0.1+11-windows-amd64-full.zip")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Build using pre-downloaded JDKs
|
|
||||||
else if(hasTargetJdks) {
|
|
||||||
println("Building all runtime images using pre-downloaded JDKs")
|
|
||||||
if(file(jdk_linux_aarch64).exists()) {
|
|
||||||
targetPlatform(targetLinuxAarch64, jdk_linux_aarch64)
|
|
||||||
}
|
|
||||||
if(file(jdk_linux_x86_64).exists()) {
|
|
||||||
targetPlatform(targetLinuxX86_64, jdk_linux_x86_64)
|
|
||||||
}
|
|
||||||
if(file(jdk_osx_aarch64).exists()) {
|
|
||||||
targetPlatform(targetOsxAarch64, jdk_osx_aarch64)
|
|
||||||
}
|
|
||||||
if(file(jdk_osx_x86_64).exists()) {
|
|
||||||
targetPlatform(targetOsxX86_64, jdk_osx_x86_64)
|
|
||||||
}
|
|
||||||
if(file(jdk_windows_x86_64).exists()) {
|
|
||||||
targetPlatform(targetWindowsX86_64, jdk_windows_x86_64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Build using locally installed JDK only
|
|
||||||
else {
|
|
||||||
println("Building a single runtime image using locally installed JDK")
|
|
||||||
}
|
|
||||||
|
|
||||||
//Additional modules to include with auto-detected modules.
|
//Additional modules to include with auto-detected modules.
|
||||||
modules = ['jdk.crypto.ec'] //jdk.crypto.ec needed for HTTPS connections (broadcastify calls & map tile server)
|
//jdk.crypto.ec - needed for HTTPS connections (broadcastify calls & map tile server)
|
||||||
|
//jdk.incubator.vector - needed for Project Panama foreign function and vector apis
|
||||||
|
//jdk.accessibility is used with assistive technologies like screen readers
|
||||||
|
rt.extension.addModules('jdk.crypto.ec', 'jdk.incubator.vector', 'jdk.accessibility')
|
||||||
|
|
||||||
//Use auto-detected modules and 'add' any specified modules.
|
//Use auto-detected modules and 'add' any specified modules.
|
||||||
additive = true
|
rt.extension.additive.set(true)
|
||||||
|
|
||||||
//Recommended options for reducing/shrinking runtime target image size
|
//Recommended options for reducing/shrinking runtime target image size
|
||||||
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
|
rt.extension.addOptions('--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages')
|
||||||
|
|
||||||
//Runtime images have auto appended version in image zip name. Override to include version in single target image.
|
//Runtime images have auto appended version in image zip name. Override to include version in single target image.
|
||||||
imageZip = (hasTargetJdks | downloadJdks) ? file("$buildDir/image/sdr-trunk.zip") :
|
rt.extension.imageZip.set(file("$buildDir/image/sdr-trunk.zip"))
|
||||||
file("$buildDir/image/sdr-trunk-" + version + ".zip")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a release package for the current operating system.
|
||||||
|
*
|
||||||
|
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
|
||||||
|
*
|
||||||
|
* Usage: gradle runtimeZipCurrent
|
||||||
|
* Usage: gradlew runtimeZipCurrent (windows - using gradlew)
|
||||||
|
* Usage: ./gradlew runtimeZipCurrent (linux - using gradlew)
|
||||||
|
*/
|
||||||
|
tasks.register('runtimeZipCurrent', org.beryx.runtime.RuntimeZipTask) {rt ->
|
||||||
|
def arch = System.properties.getProperty("os.arch")
|
||||||
|
def javaHome = org.gradle.internal.jvm.Jvm.current().getJavaHome().toString()
|
||||||
|
|
||||||
|
if(os.isWindows()) {
|
||||||
|
rt.extension.targetPlatform(targetWindowsX86_64, javaHome)
|
||||||
|
configure(rt, jvmArgsWindows)
|
||||||
|
}
|
||||||
|
else if(os.isLinux())
|
||||||
|
{
|
||||||
|
if(arch.equals("amd64")) {
|
||||||
|
rt.extension.targetPlatform(targetLinuxX86_64, javaHome)
|
||||||
|
}
|
||||||
|
else if(arch.equals("aarch64")) {
|
||||||
|
rt.extension.targetPlatform(targetLinuxAarch64, javaHome)
|
||||||
|
}
|
||||||
|
configure(rt, jvmArgsLinux)
|
||||||
|
}
|
||||||
|
else if(os.isMacOsX())
|
||||||
|
{
|
||||||
|
if(arch.equals("amd64")) {
|
||||||
|
rt.extension.targetPlatform(targetOsxX86_64, javaHome)
|
||||||
|
}
|
||||||
|
else if(arch.equals("aarch64")) {
|
||||||
|
rt.extension.targetPlatform(targetOsxAarch64, javaHome)
|
||||||
|
}
|
||||||
|
configure(rt, jvmArgsLinux)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Windows release package.
|
||||||
|
*
|
||||||
|
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
|
||||||
|
*
|
||||||
|
* Usage: gradle runtimeZipWindows
|
||||||
|
* Usage: gradlew runtimeZipWindows (windows - using gradlew)
|
||||||
|
* Usage: ./gradlew runtimeZipWindows (linux - using gradlew)
|
||||||
|
*/
|
||||||
|
tasks.register('runtimeZipWindows', org.beryx.runtime.RuntimeZipTask) {rt ->
|
||||||
|
if(file(jdk_windows_x86_64).exists()) {
|
||||||
|
rt.extension.targetPlatform(targetWindowsX86_64, jdk_windows_x86_64, [])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rt.extension.targetPlatform(targetWindowsX86_64) {
|
||||||
|
jdkHome = jdkDownload(jdk_download_windows_x86_64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configure(rt, jvmArgsWindows)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates Linux & OSX release packages.
|
||||||
|
*
|
||||||
|
* Note: you cannot create both Windows and Linux releases at the same time due to limitations of the Runtime plugin
|
||||||
|
*
|
||||||
|
* Usage: gradle runtimeZipOthers
|
||||||
|
* Usage: gradlew runtimeZipOthers (windows - using gradlew)
|
||||||
|
* Usage: ./gradlew runtimeZipOthers (linux - using gradlew)
|
||||||
|
*/
|
||||||
|
tasks.register('runtimeZipOthers', org.beryx.runtime.RuntimeZipTask) {rt ->
|
||||||
|
//Linux aarch64
|
||||||
|
if(file(jdk_linux_aarch64).exists()) {
|
||||||
|
rt.extension.targetPlatform(targetLinuxAarch64, jdk_linux_aarch64, [])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rt.extension.targetPlatform(targetLinuxAarch64) {
|
||||||
|
jdkHome = jdkDownload(jdk_download_linux_aarch64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Linux x86-64
|
||||||
|
if(file(jdk_linux_x86_64).exists()) {
|
||||||
|
rt.extension.targetPlatform(targetLinuxX86_64, jdk_linux_x86_64, [])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rt.extension.targetPlatform(targetLinuxX86_64) {
|
||||||
|
jdkHome = jdkDownload(jdk_download_linux_x86_64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//OSX aarch64
|
||||||
|
if(file(jdk_osx_aarch64).exists()) {
|
||||||
|
rt.extension.targetPlatform(targetOsxAarch64, jdk_osx_aarch64, [])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rt.extension.targetPlatform(targetOsxAarch64) {
|
||||||
|
jdkHome = jdkDownload(jdk_download_osx_aarch64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Linux x86-64
|
||||||
|
if(file(jdk_osx_x86_64).exists()) {
|
||||||
|
rt.extension.targetPlatform(targetOsxX86_64, jdk_osx_x86_64, [])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rt.extension.targetPlatform(targetOsxX86_64) {
|
||||||
|
jdkHome = jdkDownload(jdk_download_osx_x86_64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configure(rt, jvmArgsLinux);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.audio;
|
package io.github.dsheirer.audio;
|
||||||
@@ -106,7 +105,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
|||||||
* Gets the current audio segment, or creates a new audio segment as necessary and broadcasts it to any registered
|
* Gets the current audio segment, or creates a new audio segment as necessary and broadcasts it to any registered
|
||||||
* listener(s).
|
* listener(s).
|
||||||
*/
|
*/
|
||||||
protected AudioSegment getAudioSegment()
|
public AudioSegment getAudioSegment()
|
||||||
{
|
{
|
||||||
synchronized(this)
|
synchronized(this)
|
||||||
{
|
{
|
||||||
@@ -135,7 +134,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addAudio(float[] audioBuffer)
|
public void addAudio(float[] audioBuffer)
|
||||||
{
|
{
|
||||||
AudioSegment audioSegment = getAudioSegment();
|
AudioSegment audioSegment = getAudioSegment();
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* sdr-trunk
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
* it under the terms of the GNU General Public License as published by
|
||||||
* later version.
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
* This program is distributed in the hope that it will be useful,
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along with this program.
|
* You should have received a copy of the GNU General Public License
|
||||||
* If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
* ****************************************************************************
|
||||||
******************************************************************************/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.audio;
|
package io.github.dsheirer.audio;
|
||||||
|
|
||||||
@@ -26,15 +29,6 @@ import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
|||||||
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
import io.github.dsheirer.identifier.MutableIdentifierCollection;
|
||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import javafx.beans.property.BooleanProperty;
|
|
||||||
import javafx.beans.property.IntegerProperty;
|
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableSet;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -43,6 +37,14 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableSet;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audio segment containing all related metadata and a dynamic collection of audio packets. An audio segment can be
|
* Audio segment containing all related metadata and a dynamic collection of audio packets. An audio segment can be
|
||||||
@@ -138,6 +140,15 @@ public class AudioSegment implements Listener<IdentifierUpdateNotification>
|
|||||||
return mComplete;
|
return mComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this audio segment is completed.
|
||||||
|
* @return true if completed.
|
||||||
|
*/
|
||||||
|
public boolean isComplete()
|
||||||
|
{
|
||||||
|
return mComplete.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duplicate call audio property. This flag is set to true whenever a duplicate call detection function detects
|
* Duplicate call audio property. This flag is set to true whenever a duplicate call detection function detects
|
||||||
* an audio segment is a duplicate.
|
* an audio segment is a duplicate.
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public enum BroadcastState
|
|||||||
/**
|
/**
|
||||||
* Specified mount point is already in use
|
* Specified mount point is already in use
|
||||||
*/
|
*/
|
||||||
MOUNT_POINT_IN_USE("Mount Point In Use", true),
|
MOUNT_POINT_IN_USE("Mount Point In Use", false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Network is unavailable or the server address cannot be resolved
|
* Network is unavailable or the server address cannot be resolved
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.audio.codec.mbe;
|
package io.github.dsheirer.audio.codec.mbe;
|
||||||
@@ -32,11 +29,6 @@ import io.github.dsheirer.message.IMessageListener;
|
|||||||
import io.github.dsheirer.preference.PreferenceType;
|
import io.github.dsheirer.preference.PreferenceType;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import jmbe.iface.IAudioCodec;
|
|
||||||
import jmbe.iface.IAudioCodecLibrary;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -44,15 +36,20 @@ import java.net.URLClassLoader;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import jmbe.iface.IAudioCodec;
|
||||||
|
import jmbe.iface.IAudioCodecLibrary;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public abstract class JmbeAudioModule extends AbstractAudioModule implements Listener<IMessage>, IMessageListener,
|
public abstract class JmbeAudioModule extends AbstractAudioModule implements Listener<IMessage>, IMessageListener,
|
||||||
ISquelchStateListener
|
ISquelchStateListener
|
||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(JmbeAudioModule.class);
|
private static final Logger mLog = LoggerFactory.getLogger(JmbeAudioModule.class);
|
||||||
private static final String JMBE_AUDIO_LIBRARY = "JMBE";
|
private static final String JMBE_AUDIO_LIBRARY = "JMBE";
|
||||||
private static List<String> mLibraryLoadStatusLogged = new ArrayList<>();
|
private static final List<String> mLibraryLoadStatusLogged = new ArrayList<>();
|
||||||
private IAudioCodec mAudioCodec;
|
private IAudioCodec mAudioCodec;
|
||||||
private UserPreferences mUserPreferences;
|
private final UserPreferences mUserPreferences;
|
||||||
|
private static Class sLoadedJmbeAudioConverterClass;
|
||||||
|
|
||||||
public JmbeAudioModule(UserPreferences userPreferences, AliasList aliasList, int timeslot)
|
public JmbeAudioModule(UserPreferences userPreferences, AliasList aliasList, int timeslot)
|
||||||
{
|
{
|
||||||
@@ -70,7 +67,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
mAudioCodec = null;
|
mAudioCodec = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IAudioCodec getAudioCodec()
|
public IAudioCodec getAudioCodec()
|
||||||
{
|
{
|
||||||
return mAudioCodec;
|
return mAudioCodec;
|
||||||
}
|
}
|
||||||
@@ -110,38 +107,67 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
protected abstract String getCodecName();
|
protected abstract String getCodecName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads audio frame processing chain. Constructs an imbe targetdataline
|
* Loads JMBE audio converter library class and then instantiates new converter instances from the loaded class.
|
||||||
* to receive the raw imbe frames. Adds an IMBE to 8k PCM format conversion
|
|
||||||
* stream wrapper. Finally, adds an upsampling (8k to 48k) stream wrapper.
|
|
||||||
*/
|
*/
|
||||||
protected void loadConverter()
|
protected void loadConverter()
|
||||||
{
|
{
|
||||||
IAudioCodec audioConverter = null;
|
IAudioCodec audioConverter = null;
|
||||||
|
|
||||||
Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary();
|
if(sLoadedJmbeAudioConverterClass == null)
|
||||||
|
|
||||||
if(path != null)
|
|
||||||
{
|
{
|
||||||
try
|
Path path = mUserPreferences.getJmbeLibraryPreference().getPathJmbeLibrary();
|
||||||
|
|
||||||
|
if(path != null)
|
||||||
{
|
{
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
{
|
{
|
||||||
mLog.info("Loading JMBE library from [" + path.toString() + "]");
|
mLog.info("Loading JMBE library from [" + path + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
URLClassLoader childClassLoader = new URLClassLoader(new URL[]{path.toUri().toURL()},
|
try
|
||||||
this.getClass().getClassLoader());
|
|
||||||
|
|
||||||
Class classToLoad = Class.forName("jmbe.JMBEAudioLibrary", true, childClassLoader);
|
|
||||||
|
|
||||||
Object instance = classToLoad.getDeclaredConstructor().newInstance();
|
|
||||||
|
|
||||||
if(instance instanceof IAudioCodecLibrary)
|
|
||||||
{
|
{
|
||||||
IAudioCodecLibrary library = (IAudioCodecLibrary)instance;
|
URLClassLoader childClassLoader = new URLClassLoader(new URL[]{path.toUri().toURL()},
|
||||||
|
this.getClass().getClassLoader());
|
||||||
|
|
||||||
|
sLoadedJmbeAudioConverterClass = Class.forName("jmbe.JMBEAudioLibrary", true, childClassLoader);
|
||||||
|
}
|
||||||
|
catch(IllegalArgumentException iae)
|
||||||
|
{
|
||||||
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY + getCodecName()))
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't load JMBE audio conversion library - " + iae.getMessage());
|
||||||
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY + getCodecName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(MalformedURLException mue)
|
||||||
|
{
|
||||||
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't load JMBE audio conversion library from path [" + path + "]");
|
||||||
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(ClassNotFoundException e1)
|
||||||
|
{
|
||||||
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't load JMBE audio conversion library - class not found");
|
||||||
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sLoadedJmbeAudioConverterClass != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Object instance = sLoadedJmbeAudioConverterClass.getDeclaredConstructor().newInstance();
|
||||||
|
|
||||||
|
if(instance instanceof IAudioCodecLibrary library)
|
||||||
|
{
|
||||||
if((library.getMajorVersion() == 1 && library.getMinorVersion() >= 0 &&
|
if((library.getMajorVersion() == 1 && library.getMinorVersion() >= 0 &&
|
||||||
library.getBuildVersion() >= 0) || library.getMajorVersion() >= 1)
|
library.getBuildVersion() >= 0) || library.getMajorVersion() >= 1)
|
||||||
{
|
{
|
||||||
audioConverter = library.getAudioConverter(getCodecName());
|
audioConverter = library.getAudioConverter(getCodecName());
|
||||||
|
|
||||||
@@ -169,38 +195,6 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(IllegalArgumentException iae)
|
|
||||||
{
|
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY + getCodecName()))
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't load JMBE audio conversion library - " + iae.getMessage());
|
|
||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY + getCodecName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(NoSuchMethodException nsme)
|
|
||||||
{
|
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't load JMBE audio conversion library - no such method exception");
|
|
||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(MalformedURLException mue)
|
|
||||||
{
|
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't load JMBE audio conversion library from path [" + path + "]");
|
|
||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(ClassNotFoundException e1)
|
|
||||||
{
|
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
|
||||||
{
|
|
||||||
mLog.error("Couldn't load JMBE audio conversion library - class not found");
|
|
||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(InvocationTargetException ite)
|
catch(InvocationTargetException ite)
|
||||||
{
|
{
|
||||||
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
@@ -225,6 +219,14 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(NoSuchMethodException nsme)
|
||||||
|
{
|
||||||
|
if(!mLibraryLoadStatusLogged.contains(JMBE_AUDIO_LIBRARY))
|
||||||
|
{
|
||||||
|
mLog.error("Couldn't load JMBE audio conversion library - no such method exception");
|
||||||
|
mLibraryLoadStatusLogged.add(JMBE_AUDIO_LIBRARY);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -235,13 +237,6 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(audioConverter != null)
|
mAudioCodec = audioConverter;
|
||||||
{
|
|
||||||
mAudioCodec = audioConverter;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mAudioCodec = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,46 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.audio.codec.mbe;
|
package io.github.dsheirer.audio.codec.mbe;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.github.dsheirer.alias.AliasList;
|
||||||
|
import io.github.dsheirer.audio.AudioSegment;
|
||||||
|
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||||
|
import io.github.dsheirer.module.decode.p25.audio.P25P1AudioModule;
|
||||||
|
import io.github.dsheirer.module.decode.p25.audio.P25P1CallSequenceRecorder;
|
||||||
|
import io.github.dsheirer.module.decode.p25.audio.VoiceFrame;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.radio.APCO25RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
|
||||||
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
|
import io.github.dsheirer.record.AudioSegmentRecorder;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Collection;
|
||||||
|
import jmbe.iface.IAudioCodec;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.io.filefilter.DirectoryFileFilter;
|
||||||
|
import org.apache.commons.io.filefilter.SuffixFileFilter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -32,79 +51,130 @@ public class MBECallSequenceConverter
|
|||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(MBECallSequenceConverter.class);
|
private final static Logger mLog = LoggerFactory.getLogger(MBECallSequenceConverter.class);
|
||||||
|
|
||||||
// public static void convert(Path input, Path output) throws IOException
|
/**
|
||||||
// {
|
* Converts the input MBE file to PCM audio and writes to the output wave file.
|
||||||
// InputStream inputStream = Files.newInputStream(input);
|
* @param input path to the MBE file.
|
||||||
// ObjectMapper mapper = new ObjectMapper();
|
* @param output path to write the WAVE recording.
|
||||||
// MBECallSequence sequence = mapper.readValue(inputStream, MBECallSequence.class);
|
* @throws IOException if there is an error.
|
||||||
// convert(sequence, output);
|
*/
|
||||||
// }
|
public static void convert(Path input, Path output) throws IOException
|
||||||
//
|
{
|
||||||
// public static void convert(MBECallSequence callSequence, Path outputPath)
|
InputStream inputStream = Files.newInputStream(input);
|
||||||
// {
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
// if(callSequence == null || callSequence.isEncrypted())
|
MBECallSequence sequence = mapper.readValue(inputStream, MBECallSequence.class);
|
||||||
// {
|
convert(sequence, output);
|
||||||
// throw new IllegalArgumentException("Cannot decode null or encrypted call sequence");
|
}
|
||||||
// }
|
|
||||||
//
|
public static void convert(MBECallSequence callSequence, Path outputPath)
|
||||||
// if(callSequence != null && !callSequence.isEncrypted())
|
{
|
||||||
// {
|
if(callSequence == null || callSequence.isEncrypted())
|
||||||
// ThumbDv.AudioProtocol protocol = ThumbDv.AudioProtocol.P25_PHASE2;
|
{
|
||||||
//
|
throw new IllegalArgumentException("Cannot decode null or encrypted call sequence");
|
||||||
// AudioPacketWaveRecorder recorder = new AudioPacketWaveRecorder(outputPath);
|
}
|
||||||
// recorder.start();
|
|
||||||
//
|
if(callSequence != null)
|
||||||
// long delayMillis = 0;
|
{
|
||||||
//
|
if(callSequence.getProtocol().equals(P25P1CallSequenceRecorder.PROTOCOL))
|
||||||
// try(ThumbDv thumbDv = new ThumbDv(protocol, recorder))
|
{
|
||||||
// {
|
P25P1AudioModule audioModule = new P25P1AudioModule(new UserPreferences(), new AliasList("mbe generator"));
|
||||||
// thumbDv.start();
|
audioModule.setRecordAudio(true);
|
||||||
// for(VoiceFrame voiceFrame: callSequence.getVoiceFrames())
|
audioModule.start();
|
||||||
// {
|
|
||||||
// mLog.debug("Frame [" + voiceFrame.getFrame() + "] + Hex [" + AmbeResponse.toHex(voiceFrame.getFrameBytes()) + "]");
|
if(callSequence.getFromIdentifier() != null)
|
||||||
// thumbDv.decode(voiceFrame.getFrameBytes());
|
{
|
||||||
// delayMillis += 30;
|
int from = 0;
|
||||||
// }
|
|
||||||
//
|
try
|
||||||
// if(delayMillis > 0)
|
{
|
||||||
// {
|
from = Integer.parseInt(callSequence.getFromIdentifier());
|
||||||
// delayMillis += 1000;
|
audioModule.getIdentifierUpdateListener().receive(new IdentifierUpdateNotification(APCO25RadioIdentifier.createFrom(from),
|
||||||
// try
|
IdentifierUpdateNotification.Operation.ADD, 0));
|
||||||
// {
|
}
|
||||||
// Thread.sleep(delayMillis);
|
catch(Exception e)
|
||||||
// }
|
{
|
||||||
// catch(InterruptedException ie)
|
mLog.error("Error parsing from identifier from value [" + callSequence.getFromIdentifier());
|
||||||
// {
|
}
|
||||||
//
|
}
|
||||||
// }
|
|
||||||
// }
|
if(callSequence.getToIdentifier() != null)
|
||||||
// }
|
{
|
||||||
// catch(IOException ioe)
|
int to = 0;
|
||||||
// {
|
|
||||||
// mLog.error("Error", ioe);
|
try
|
||||||
// }
|
{
|
||||||
//
|
to = Integer.parseInt(callSequence.getToIdentifier());
|
||||||
// recorder.stop(Paths.get(outputPath.toString().replace(".tmp", ".wav")), new WaveMetadata());
|
audioModule.getIdentifierUpdateListener().receive(new IdentifierUpdateNotification(APCO25Talkgroup.create(to),
|
||||||
// }
|
IdentifierUpdateNotification.Operation.ADD, 0));
|
||||||
// }
|
}
|
||||||
//
|
catch(Exception e)
|
||||||
// public static void main(String[] args)
|
{
|
||||||
// {
|
mLog.error("Error parsing from identifier from value [" + callSequence.getFromIdentifier());
|
||||||
// String mbe = "/home/denny/SDRTrunk/recordings/20190331085324_154250000_3_TS0_65084_6591007.mbe";
|
}
|
||||||
// String mbe = "/home/denny/SDRTrunk/recordings/20190331085324_154250000_2_TS1_65035.mbe";
|
}
|
||||||
//
|
|
||||||
// Path input = Paths.get(mbe);
|
IAudioCodec codec = audioModule.getAudioCodec();
|
||||||
// Path output = Paths.get(mbe.replace(".mbe", ".tmp"));
|
|
||||||
//
|
for(VoiceFrame voiceFrame: callSequence.getVoiceFrames())
|
||||||
// mLog.info("Converting: " + mbe);
|
{
|
||||||
//
|
byte[] frameBytes = voiceFrame.getFrameBytes();
|
||||||
// try
|
float[] audio = codec.getAudio(frameBytes);
|
||||||
// {
|
audioModule.addAudio(audio);
|
||||||
// MBECallSequenceConverter.convert(input, output);
|
}
|
||||||
// }
|
|
||||||
// catch(IOException ioe)
|
AudioSegment audioSegment = audioModule.getAudioSegment();
|
||||||
// {
|
|
||||||
// mLog.error("Error", ioe);
|
try
|
||||||
// }
|
{
|
||||||
// }
|
AudioSegmentRecorder.recordWAVE(audioSegment, outputPath);
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
mLog.error("Error writing audio segment, ioe");
|
||||||
|
}
|
||||||
|
|
||||||
|
audioModule.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
boolean all = true;
|
||||||
|
|
||||||
|
String path = "/home/denny/SDRTrunk/recordings";
|
||||||
|
Path input = Paths.get(path);
|
||||||
|
|
||||||
|
if(all)
|
||||||
|
{
|
||||||
|
Collection<File> mbeFiles = FileUtils.listFiles(input.toFile(), new SuffixFileFilter(".mbe"), DirectoryFileFilter.DIRECTORY);
|
||||||
|
|
||||||
|
for(File inputFile: mbeFiles)
|
||||||
|
{
|
||||||
|
Path output = Paths.get(inputFile.getAbsolutePath().replace(".mbe", ".wav"));
|
||||||
|
mLog.info("Converting: " + inputFile);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MBECallSequenceConverter.convert(inputFile.toPath(), output);
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
mLog.error("Error", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Path output = Paths.get(path.replace(".mbe", ".wav"));
|
||||||
|
mLog.info("Converting: " + path);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MBECallSequenceConverter.convert(input, output);
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
mLog.error("Error", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,27 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.audio.playback;
|
package io.github.dsheirer.audio.playback;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import io.github.dsheirer.alias.id.priority.Priority;
|
|
||||||
import io.github.dsheirer.audio.AudioEvent;
|
import io.github.dsheirer.audio.AudioEvent;
|
||||||
import io.github.dsheirer.audio.AudioSegment;
|
import io.github.dsheirer.audio.AudioSegment;
|
||||||
|
import io.github.dsheirer.controller.NamingThreadFactory;
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
import io.github.dsheirer.eventbus.MyEventBus;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
import io.github.dsheirer.identifier.IdentifierUpdateNotification;
|
||||||
@@ -33,11 +30,13 @@ import io.github.dsheirer.preference.UserPreferences;
|
|||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.source.mixer.MixerChannel;
|
import io.github.dsheirer.source.mixer.MixerChannel;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import java.nio.ByteBuffer;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import java.util.concurrent.Executors;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -51,10 +50,6 @@ import javax.sound.sampled.LineListener;
|
|||||||
import javax.sound.sampled.LineUnavailableException;
|
import javax.sound.sampled.LineUnavailableException;
|
||||||
import javax.sound.sampled.Mixer;
|
import javax.sound.sampled.Mixer;
|
||||||
import javax.sound.sampled.SourceDataLine;
|
import javax.sound.sampled.SourceDataLine;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audio output/playback channel for a single audio mixer channel. Providers support for playback of audio segments
|
* Audio output/playback channel for a single audio mixer channel. Providers support for playback of audio segments
|
||||||
@@ -67,7 +62,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
private int mBufferStopThreshold;
|
private int mBufferStopThreshold;
|
||||||
private Listener<IdentifierCollection> mIdentifierCollectionListener;
|
private Listener<IdentifierCollection> mIdentifierCollectionListener;
|
||||||
private Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
private Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
||||||
private ScheduledFuture<?> mProcessorFuture;
|
|
||||||
private SourceDataLine mOutput;
|
private SourceDataLine mOutput;
|
||||||
private Mixer mMixer;
|
private Mixer mMixer;
|
||||||
private MixerChannel mMixerChannel;
|
private MixerChannel mMixerChannel;
|
||||||
@@ -76,17 +70,17 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
private AudioEvent mAudioStartEvent;
|
private AudioEvent mAudioStartEvent;
|
||||||
private AudioEvent mAudioStopEvent;
|
private AudioEvent mAudioStopEvent;
|
||||||
private boolean mCanProcessAudio = false;
|
private boolean mCanProcessAudio = false;
|
||||||
|
private LinkedTransferQueue<AudioSegment> mAudioSegmentQueue = new LinkedTransferQueue<>();
|
||||||
private AudioSegment mCurrentAudioSegment;
|
private AudioSegment mCurrentAudioSegment;
|
||||||
private AudioSegment mNextAudioSegment;
|
|
||||||
private ReentrantLock mLock = new ReentrantLock();
|
|
||||||
private int mCurrentBufferIndex = 0;
|
private int mCurrentBufferIndex = 0;
|
||||||
private UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
private BooleanProperty mEmptyProperty = new SimpleBooleanProperty(true);
|
|
||||||
private IntegerProperty mAudioPriority = new SimpleIntegerProperty(Priority.DEFAULT_PRIORITY);
|
|
||||||
private ByteBuffer mAudioSegmentStartTone;
|
private ByteBuffer mAudioSegmentStartTone;
|
||||||
private ByteBuffer mAudioSegmentPreemptTone;
|
|
||||||
private ByteBuffer mAudioSegmentDropTone;
|
private ByteBuffer mAudioSegmentDropTone;
|
||||||
private boolean mRunning = false;
|
private boolean mRunning = false;
|
||||||
|
private ScheduledExecutorService mScheduledExecutorService;
|
||||||
|
private ScheduledFuture<?> mProcessorFuture;
|
||||||
|
private long mOutputLastTimestamp = 0;
|
||||||
|
private static final long STALE_PLAYBACK_THRESHOLD_MS = 500;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Single audio channel playback with automatic starting and stopping of the
|
* Single audio channel playback with automatic starting and stopping of the
|
||||||
@@ -107,6 +101,8 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
{
|
{
|
||||||
mMixer = mixer;
|
mMixer = mixer;
|
||||||
mMixerChannel = mixerChannel;
|
mMixerChannel = mixerChannel;
|
||||||
|
mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory(
|
||||||
|
"sdrtrunk audio output " + mixerChannel.name()));
|
||||||
mUserPreferences = userPreferences;
|
mUserPreferences = userPreferences;
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -150,7 +146,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Run the queue processor task every 100 milliseconds or 10 times a second
|
//Run the queue processor task every 100 milliseconds or 10 times a second
|
||||||
mProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
mProcessorFuture = mScheduledExecutorService.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
||||||
0, 100, TimeUnit.MILLISECONDS);
|
0, 100, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,26 +168,17 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boolean property that indicates if this audio output has an audio segment queued or in process of playback.
|
* Indicates if this audio output doesn't currently have any audio segments queued for playback.
|
||||||
|
* @return true if empty
|
||||||
*/
|
*/
|
||||||
public BooleanProperty emptyProperty()
|
public boolean isEmpty()
|
||||||
{
|
{
|
||||||
return mEmptyProperty;
|
return mAudioSegmentQueue.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audio playback priority of the current audio segment or default audio priority if nothing is currently loaded.
|
* Schedules the audio segment for playback. The audio segment user count should already be incremented by the
|
||||||
*/
|
* calling entity.
|
||||||
public IntegerProperty audioPriorityProperty()
|
|
||||||
{
|
|
||||||
return mAudioPriority;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedules the audio segment for playback.
|
|
||||||
*
|
|
||||||
* Note: if a segment is currently playing and another audio segment is already queued for playback, invoking
|
|
||||||
* this method will overwrite the queued segment with the argument.
|
|
||||||
*
|
*
|
||||||
* @param audioSegment to schedule for playback.
|
* @param audioSegment to schedule for playback.
|
||||||
*/
|
*/
|
||||||
@@ -199,23 +186,8 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
{
|
{
|
||||||
if(audioSegment != null)
|
if(audioSegment != null)
|
||||||
{
|
{
|
||||||
mLock.lock();
|
//Audio segment use count has already been incremented by the external caller.
|
||||||
|
mAudioSegmentQueue.add(audioSegment);
|
||||||
if(mNextAudioSegment != null)
|
|
||||||
{
|
|
||||||
mNextAudioSegment.decrementConsumerCount();
|
|
||||||
mNextAudioSegment = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mNextAudioSegment = audioSegment;
|
|
||||||
mEmptyProperty.set(false);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
mLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +234,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
private void updateToneInsertionAudioClips()
|
private void updateToneInsertionAudioClips()
|
||||||
{
|
{
|
||||||
mAudioSegmentStartTone = null;
|
mAudioSegmentStartTone = null;
|
||||||
mAudioSegmentPreemptTone = null;
|
|
||||||
mAudioSegmentDropTone = null;
|
mAudioSegmentDropTone = null;
|
||||||
|
|
||||||
float[] start = mUserPreferences.getPlaybackPreference().getStartTone();
|
float[] start = mUserPreferences.getPlaybackPreference().getStartTone();
|
||||||
@@ -272,13 +243,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
mAudioSegmentStartTone = convert(start);
|
mAudioSegmentStartTone = convert(start);
|
||||||
}
|
}
|
||||||
|
|
||||||
float[] preempt = mUserPreferences.getPlaybackPreference().getPreemptTone();
|
|
||||||
|
|
||||||
if(preempt != null)
|
|
||||||
{
|
|
||||||
mAudioSegmentPreemptTone = convert(preempt);
|
|
||||||
}
|
|
||||||
|
|
||||||
float[] drop = mUserPreferences.getPlaybackPreference().getDropTone();
|
float[] drop = mUserPreferences.getPlaybackPreference().getDropTone();
|
||||||
|
|
||||||
if(drop != null)
|
if(drop != null)
|
||||||
@@ -287,48 +251,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Disposes of a processed audio segment and notifies the originator that processing of the segment is complete
|
|
||||||
* by decrementing the consumer count.
|
|
||||||
* @param audioSegment to dispose
|
|
||||||
*/
|
|
||||||
private void dispose(AudioSegment audioSegment)
|
|
||||||
{
|
|
||||||
mAudioPriority.unbind();
|
|
||||||
|
|
||||||
if(audioSegment != null)
|
|
||||||
{
|
|
||||||
audioSegment.decrementConsumerCount();
|
|
||||||
audioSegment.removeIdentifierUpdateNotificationListener(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a tone indicating that a new audio segment is starting
|
|
||||||
*/
|
|
||||||
private ByteBuffer getAudioSegmentStartTone()
|
|
||||||
{
|
|
||||||
return mAudioSegmentStartTone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a tone indicating that the current audio segment playback has been preempted for a higher priority
|
|
||||||
* audio segment that is now starting.
|
|
||||||
*/
|
|
||||||
private ByteBuffer getAudioSegmentPreemptionTone()
|
|
||||||
{
|
|
||||||
return mAudioSegmentPreemptTone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a tone indicating that the current audio segment playback has been dropped because the audio segment
|
|
||||||
* has been flagged as Do Not Monitor after playback has started.
|
|
||||||
*/
|
|
||||||
private ByteBuffer getAudioSegmentDropTone()
|
|
||||||
{
|
|
||||||
return mAudioSegmentDropTone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the audio buffer data to the source data line. If the data line is not currently playing, write as
|
* Writes the audio buffer data to the source data line. If the data line is not currently playing, write as
|
||||||
* much of the buffer to the data line as will fit, start the dataline, and then finish writing the residual
|
* much of the buffer to the data line as will fit, start the dataline, and then finish writing the residual
|
||||||
@@ -355,6 +277,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
|
|
||||||
//Top off the buffer and check if we can start it
|
//Top off the buffer and check if we can start it
|
||||||
wrote += mOutput.write(buffer.array(), 0, toWrite);
|
wrote += mOutput.write(buffer.array(), 0, toWrite);
|
||||||
|
mOutputLastTimestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
checkStart();
|
checkStart();
|
||||||
}
|
}
|
||||||
@@ -363,147 +286,135 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
{
|
{
|
||||||
//This will block until the buffer is fully written to the data line
|
//This will block until the buffer is fully written to the data line
|
||||||
mOutput.write(buffer.array(), wrote, buffer.array().length - wrote);
|
mOutput.write(buffer.array(), wrote, buffer.array().length - wrote);
|
||||||
|
mOutputLastTimestamp = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispose of the currently assigned audio segment.
|
||||||
|
*/
|
||||||
|
private void disposeCurrentAudioSegment()
|
||||||
|
{
|
||||||
|
if(mCurrentAudioSegment != null)
|
||||||
|
{
|
||||||
|
mCurrentAudioSegment.decrementConsumerCount();
|
||||||
|
mCurrentAudioSegment.removeIdentifierUpdateNotificationListener(this);
|
||||||
|
mCurrentAudioSegment = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the next audio segment from the queue.
|
||||||
|
*/
|
||||||
|
private void loadNextAudioSegment()
|
||||||
|
{
|
||||||
|
AudioSegment audioSegment = mAudioSegmentQueue.poll();
|
||||||
|
|
||||||
|
boolean verificationInProgress = (audioSegment != null);
|
||||||
|
|
||||||
|
while(verificationInProgress)
|
||||||
|
{
|
||||||
|
if(audioSegment != null)
|
||||||
|
{
|
||||||
|
//Throw away the audio segment if it has been flagged as do not monitor or is duplicate
|
||||||
|
if(isThrowaway(audioSegment))
|
||||||
|
{
|
||||||
|
audioSegment.decrementConsumerCount();
|
||||||
|
audioSegment = mAudioSegmentQueue.poll();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
verificationInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
verificationInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mCurrentAudioSegment = audioSegment;
|
||||||
|
|
||||||
|
if(audioSegment != null)
|
||||||
|
{
|
||||||
|
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
|
||||||
|
broadcast(mCurrentAudioSegment.getIdentifierCollection());
|
||||||
|
|
||||||
|
//If we played any amount of the previous audio segment then play the audio segment start. We check this
|
||||||
|
//to avoid repeatedly playing an audio segment start tone on the rare occasion that we have empty segments.
|
||||||
|
if(mCurrentBufferIndex > 0)
|
||||||
|
{
|
||||||
|
playAudio(mAudioSegmentStartTone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mCurrentBufferIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the audio segment should be thrown away because it is marked as duplicate or do-not-monitor.
|
||||||
|
* @param audioSegment to evaluate
|
||||||
|
* @return true if the audio should be thrown away.
|
||||||
|
*/
|
||||||
|
private boolean isThrowaway(AudioSegment audioSegment)
|
||||||
|
{
|
||||||
|
return audioSegment != null &&
|
||||||
|
(audioSegment.isDoNotMonitor() ||
|
||||||
|
(audioSegment.isDuplicate() && mUserPreferences.getDuplicateCallDetectionPreference()
|
||||||
|
.isDuplicatePlaybackSuppressionEnabled()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage audio segment playback and process audio segment buffers. This method is designed to be called
|
* Manage audio segment playback and process audio segment buffers. This method is designed to be called
|
||||||
* by a threaded processor repeatedly to playback the current audio segment and check for and start a newly
|
* by a threaded processor repeatedly to playback the current audio segment and check for and start newly
|
||||||
* assigned audio segment. It also handles starting and stopping the playback source data line to avoid audio
|
* added audio segments. It also handles starting and stopping the playback source data line to avoid audio
|
||||||
* discontinuities due to buffer underruns.
|
* discontinuities due to buffer underruns.
|
||||||
*/
|
*/
|
||||||
private void processAudio()
|
private void processAudio()
|
||||||
{
|
{
|
||||||
if(mNextAudioSegment != null)
|
if(mCurrentAudioSegment == null)
|
||||||
{
|
{
|
||||||
mLock.lock();
|
loadNextAudioSegment();
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
//Remove an assigned segment that's subsequently flagged as duplicate or do not monitor before playback
|
|
||||||
if(mNextAudioSegment.isDoNotMonitor() || (mNextAudioSegment.isDuplicate() &&
|
|
||||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
|
|
||||||
{
|
|
||||||
mNextAudioSegment = null;
|
|
||||||
|
|
||||||
if(mCurrentAudioSegment == null)
|
|
||||||
{
|
|
||||||
mEmptyProperty.set(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//For linked audio segments, allow the linked segment to complete first before assigning the next
|
|
||||||
else if(mNextAudioSegment.isLinked())
|
|
||||||
{
|
|
||||||
if(mCurrentAudioSegment == null)
|
|
||||||
{
|
|
||||||
mCurrentAudioSegment = mNextAudioSegment;
|
|
||||||
mNextAudioSegment = null;
|
|
||||||
mCurrentBufferIndex = 0;
|
|
||||||
|
|
||||||
if(mCurrentAudioSegment != null)
|
|
||||||
{
|
|
||||||
mAudioPriority.bind(mCurrentAudioSegment.monitorPriorityProperty());
|
|
||||||
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
|
|
||||||
broadcast(mCurrentAudioSegment.getIdentifierCollection());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mAudioPriority.setValue(Priority.DEFAULT_PRIORITY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Insert audio segment start or audio priority preemption bonk tone
|
|
||||||
if(mCurrentAudioSegment == null)
|
|
||||||
{
|
|
||||||
playAudio(getAudioSegmentStartTone());
|
|
||||||
}
|
|
||||||
else if(mCurrentBufferIndex > 0 &&
|
|
||||||
(!mCurrentAudioSegment.completeProperty().get() ||
|
|
||||||
mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount()))
|
|
||||||
{
|
|
||||||
playAudio(getAudioSegmentPreemptionTone());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
playAudio(getAudioSegmentStartTone());
|
|
||||||
}
|
|
||||||
|
|
||||||
//Close current audio segment
|
|
||||||
dispose(mCurrentAudioSegment);
|
|
||||||
mCurrentAudioSegment = mNextAudioSegment;
|
|
||||||
mNextAudioSegment = null;
|
|
||||||
mCurrentBufferIndex = 0;
|
|
||||||
|
|
||||||
if(mCurrentAudioSegment != null)
|
|
||||||
{
|
|
||||||
mAudioPriority.bind(mCurrentAudioSegment.monitorPriorityProperty());
|
|
||||||
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
|
|
||||||
broadcast(mCurrentAudioSegment.getIdentifierCollection());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mAudioPriority.setValue(Priority.DEFAULT_PRIORITY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
mLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mCurrentAudioSegment != null)
|
//Reevaluate current audio segment to see if the status has changed for duplicate or do-not-monitor.
|
||||||
|
if(isThrowaway(mCurrentAudioSegment))
|
||||||
{
|
{
|
||||||
//Check for completed audio segment or a segment flagged as duplicate or Do Not Monitor
|
if(mCurrentBufferIndex > 0)
|
||||||
if(mCurrentAudioSegment.isDoNotMonitor() ||
|
|
||||||
(mCurrentAudioSegment.isDuplicate() &&
|
|
||||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()) ||
|
|
||||||
(mCurrentAudioSegment.completeProperty().get() && mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))
|
|
||||||
{
|
{
|
||||||
if(mCurrentAudioSegment.isDoNotMonitor())
|
playAudio(mAudioSegmentDropTone);
|
||||||
{
|
|
||||||
playAudio(getAudioSegmentDropTone());
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose(mCurrentAudioSegment);
|
|
||||||
mCurrentAudioSegment = null;
|
|
||||||
|
|
||||||
mLock.lock();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if(mNextAudioSegment == null)
|
|
||||||
{
|
|
||||||
mEmptyProperty.set(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
mLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Process any new buffers that have been added to the audio segment. If a next audio segment gets assigned
|
disposeCurrentAudioSegment();
|
||||||
//while processing, exit the loop so that we can evaluate the next for higher priority preempt. If the next
|
loadNextAudioSegment();
|
||||||
//segment is a linked segment, ignore it so that we can close out the current segment.
|
}
|
||||||
while(mCurrentAudioSegment != null && (mNextAudioSegment == null || mNextAudioSegment.isLinked()) &&
|
|
||||||
mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount() &&
|
while(mCurrentAudioSegment != null && mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount())
|
||||||
!mCurrentAudioSegment.isDoNotMonitor() && !(mCurrentAudioSegment.isDuplicate() &&
|
{
|
||||||
mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled()))
|
try
|
||||||
{
|
{
|
||||||
float[] audioBuffer = mCurrentAudioSegment.getAudioBuffers().get(mCurrentBufferIndex++);
|
float[] audioBuffer = mCurrentAudioSegment.getAudioBuffers().get(mCurrentBufferIndex++);
|
||||||
|
|
||||||
if(audioBuffer != null)
|
if(audioBuffer != null)
|
||||||
{
|
{
|
||||||
ByteBuffer audio = convert(audioBuffer);
|
ByteBuffer audio = convert(audioBuffer);
|
||||||
|
//This call blocks until all audio bytes are dumped into the data line.
|
||||||
playAudio(audio);
|
playAudio(audio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error while processing audio for [" + mMixerChannel.name() + "]", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for completed and fully-played audio segment -- load next audio segment
|
||||||
|
if(mCurrentAudioSegment != null &&
|
||||||
|
mCurrentAudioSegment.isComplete() &&
|
||||||
|
(mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))
|
||||||
|
{
|
||||||
|
disposeCurrentAudioSegment();
|
||||||
}
|
}
|
||||||
|
|
||||||
checkStop();
|
checkStop();
|
||||||
@@ -523,25 +434,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
}
|
}
|
||||||
|
|
||||||
mProcessorFuture = null;
|
mProcessorFuture = null;
|
||||||
|
disposeCurrentAudioSegment();
|
||||||
mLock.lock();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if(mNextAudioSegment != null)
|
|
||||||
{
|
|
||||||
mNextAudioSegment.decrementConsumerCount();
|
|
||||||
mNextAudioSegment = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
mLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose(mCurrentAudioSegment);
|
|
||||||
mCurrentAudioSegment = null;
|
|
||||||
|
|
||||||
mAudioEventBroadcaster.clear();
|
mAudioEventBroadcaster.clear();
|
||||||
mIdentifierCollectionListener = null;
|
mIdentifierCollectionListener = null;
|
||||||
|
|
||||||
@@ -650,14 +543,28 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
if(mRunning)
|
if(mRunning)
|
||||||
{
|
{
|
||||||
//If the output buffer falls below the threshold then drain the output and stop playback
|
//If the output buffer falls below the threshold then drain the output and stop playback
|
||||||
if(mOutput.isRunning() && mOutput.available() >= mBufferStopThreshold)
|
if(mOutput.isRunning())
|
||||||
{
|
{
|
||||||
mOutput.drain();
|
if(mOutput.available() >= mBufferStopThreshold)
|
||||||
mOutput.stop();
|
{
|
||||||
mRunning = false;
|
mOutput.drain();
|
||||||
|
mOutput.stop();
|
||||||
|
mRunning = false;
|
||||||
|
}
|
||||||
|
//Detect when we have a partially filled output that will neither start nor stop and the last write
|
||||||
|
//timestamp was more than the staleness threshold (500 ms) -- stop the output and discard stale contents
|
||||||
|
else if(mOutput.available() < mBufferStopThreshold &&
|
||||||
|
mOutput.available() > mBufferStartThreshold &&
|
||||||
|
(System.currentTimeMillis() - mOutputLastTimestamp >= STALE_PLAYBACK_THRESHOLD_MS))
|
||||||
|
{
|
||||||
|
mOutput.stop();
|
||||||
|
//Discard the stale buffer contents.
|
||||||
|
mOutput.flush();
|
||||||
|
mRunning = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//If output playback stopped on its own because the buffer emptied, then cleanup
|
//If output playback stopped on its own because the buffer emptied, then cleanup
|
||||||
else if(!mOutput.isRunning())
|
else
|
||||||
{
|
{
|
||||||
mRunning = false;
|
mRunning = false;
|
||||||
}
|
}
|
||||||
@@ -735,16 +642,33 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
*/
|
*/
|
||||||
public class AudioSegmentProcessor implements Runnable
|
public class AudioSegmentProcessor implements Runnable
|
||||||
{
|
{
|
||||||
|
private AtomicBoolean mProcessing = new AtomicBoolean();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
try
|
if(mProcessing.compareAndSet(false, true))
|
||||||
{
|
{
|
||||||
processAudio();
|
try
|
||||||
}
|
{
|
||||||
catch(Throwable t)
|
//TODO: Debug hooks - remove after testing
|
||||||
{
|
if(mMixerChannel.name().equals("LEFT"))
|
||||||
mLog.error("Error while processing audio buffers", t);
|
{
|
||||||
|
int a = 0;
|
||||||
|
}
|
||||||
|
else if(mMixerChannel.name().equals("RIGHT"))
|
||||||
|
{
|
||||||
|
int a = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
processAudio();
|
||||||
|
}
|
||||||
|
catch(Throwable t)
|
||||||
|
{
|
||||||
|
mLog.error("Error while processing audio buffers", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
mProcessing.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.audio.playback;
|
package io.github.dsheirer.audio.playback;
|
||||||
|
|
||||||
@@ -24,6 +23,7 @@ import io.github.dsheirer.audio.AudioEvent;
|
|||||||
import io.github.dsheirer.audio.AudioException;
|
import io.github.dsheirer.audio.AudioException;
|
||||||
import io.github.dsheirer.audio.AudioSegment;
|
import io.github.dsheirer.audio.AudioSegment;
|
||||||
import io.github.dsheirer.audio.IAudioController;
|
import io.github.dsheirer.audio.IAudioController;
|
||||||
|
import io.github.dsheirer.controller.NamingThreadFactory;
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
import io.github.dsheirer.eventbus.MyEventBus;
|
||||||
import io.github.dsheirer.preference.PreferenceType;
|
import io.github.dsheirer.preference.PreferenceType;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
@@ -31,17 +31,18 @@ import io.github.dsheirer.sample.Broadcaster;
|
|||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.source.mixer.MixerChannel;
|
import io.github.dsheirer.source.mixer.MixerChannel;
|
||||||
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
|
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages scheduling and playback of audio segments to the local users audio system.
|
* Manages scheduling and playback of audio segments to the local users audio system.
|
||||||
@@ -50,8 +51,6 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(AudioPlaybackManager.class);
|
private static final Logger mLog = LoggerFactory.getLogger(AudioPlaybackManager.class);
|
||||||
|
|
||||||
public static final String AUDIO_CHANNELS_PROPERTY = "audio.manager.channels";
|
|
||||||
public static final String AUDIO_MIXER_PROPERTY = "audio.manager.mixer";
|
|
||||||
public static final AudioEvent CONFIGURATION_CHANGE_STARTED =
|
public static final AudioEvent CONFIGURATION_CHANGE_STARTED =
|
||||||
new AudioEvent(AudioEvent.Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null);
|
new AudioEvent(AudioEvent.Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null);
|
||||||
public static final AudioEvent CONFIGURATION_CHANGE_COMPLETE =
|
public static final AudioEvent CONFIGURATION_CHANGE_COMPLETE =
|
||||||
@@ -64,6 +63,8 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
private List<AudioSegment> mAudioSegments = new ArrayList<>();
|
private List<AudioSegment> mAudioSegments = new ArrayList<>();
|
||||||
private List<AudioSegment> mPendingAudioSegments = new ArrayList<>();
|
private List<AudioSegment> mPendingAudioSegments = new ArrayList<>();
|
||||||
private LinkedTransferQueue<AudioSegment> mNewAudioSegmentQueue = new LinkedTransferQueue<>();
|
private LinkedTransferQueue<AudioSegment> mNewAudioSegmentQueue = new LinkedTransferQueue<>();
|
||||||
|
private ScheduledExecutorService mScheduledExecutorService =
|
||||||
|
Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory("sdrtrunk audio manager"));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance.
|
* Constructs an instance.
|
||||||
@@ -202,29 +203,13 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
//Sort audio segments by playback priority and assign to empty audio outputs
|
//Sort audio segments by playback priority and assign to empty audio outputs
|
||||||
if(!mAudioSegments.isEmpty())
|
if(!mAudioSegments.isEmpty())
|
||||||
{
|
{
|
||||||
|
//TODO: change this to take audio segment start time into account also
|
||||||
mAudioSegments.sort(Comparator.comparingInt(o -> o.monitorPriorityProperty().get()));
|
mAudioSegments.sort(Comparator.comparingInt(o -> o.monitorPriorityProperty().get()));
|
||||||
|
|
||||||
//Assign empty audio outputs first
|
//Assign empty audio outputs first
|
||||||
for(AudioOutput audioOutput: mAudioOutputs)
|
for(AudioOutput audioOutput: mAudioOutputs)
|
||||||
{
|
{
|
||||||
if(audioOutput.emptyProperty().get())
|
if(audioOutput.isEmpty())
|
||||||
{
|
|
||||||
audioOutput.play(mAudioSegments.remove(0));
|
|
||||||
|
|
||||||
if(mAudioSegments.isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Preempt ongoing audio segment playback when higher priority audio segments are available
|
|
||||||
if(!mAudioSegments.isEmpty())
|
|
||||||
{
|
|
||||||
for(AudioOutput audioOutput: mAudioOutputs)
|
|
||||||
{
|
|
||||||
if(mAudioSegments.get(0).monitorPriorityProperty().get() < audioOutput.audioPriorityProperty().get())
|
|
||||||
{
|
{
|
||||||
audioOutput.play(mAudioSegments.remove(0));
|
audioOutput.play(mAudioSegments.remove(0));
|
||||||
|
|
||||||
@@ -284,7 +269,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
}
|
}
|
||||||
catch(AudioException ae)
|
catch(AudioException ae)
|
||||||
{
|
{
|
||||||
mLog.error("Error changing audio output to [" + configuration.toString() + "]", ae);
|
mLog.error("Error changing audio output to [" + configuration + "]", ae);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,7 +317,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
throw new AudioException("Unsupported mixer channel configuration: " + entry.getMixerChannel());
|
throw new AudioException("Unsupported mixer channel configuration: " + entry.getMixerChannel());
|
||||||
}
|
}
|
||||||
|
|
||||||
mProcessingTask = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
mProcessingTask = mScheduledExecutorService.scheduleAtFixedRate(new AudioSegmentProcessor(),
|
||||||
0, 100, TimeUnit.MILLISECONDS);
|
0, 100, TimeUnit.MILLISECONDS);
|
||||||
mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_COMPLETE);
|
mControllerBroadcaster.broadcast(CONFIGURATION_CHANGE_COMPLETE);
|
||||||
mMixerChannelConfiguration = entry;
|
mMixerChannelConfiguration = entry;
|
||||||
@@ -343,7 +328,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
* Current audio playback mixer channel configuration setting.
|
* Current audio playback mixer channel configuration setting.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
|
public MixerChannelConfiguration getMixerChannelConfiguration()
|
||||||
{
|
{
|
||||||
return mMixerChannelConfiguration;
|
return mMixerChannelConfiguration;
|
||||||
}
|
}
|
||||||
@@ -384,16 +369,23 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
|||||||
*/
|
*/
|
||||||
public class AudioSegmentProcessor implements Runnable
|
public class AudioSegmentProcessor implements Runnable
|
||||||
{
|
{
|
||||||
|
private AtomicBoolean mProcessing = new AtomicBoolean();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
try
|
if(mProcessing.compareAndSet(false, true))
|
||||||
{
|
{
|
||||||
processAudioSegments();
|
try
|
||||||
}
|
{
|
||||||
catch(Throwable t)
|
processAudioSegments();
|
||||||
{
|
}
|
||||||
mLog.error("Encountered error while processing audio segments", t);
|
catch(Throwable t)
|
||||||
|
{
|
||||||
|
mLog.error("Encountered error while processing audio segments", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
mProcessing.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,40 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.bits;
|
package io.github.dsheirer.bits;
|
||||||
|
|
||||||
import io.github.dsheirer.edac.CRC;
|
import io.github.dsheirer.edac.CRC;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.BitSet;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
import org.apache.commons.math3.util.FastMath;
|
||||||
|
import org.slf4j.Logger;
|
||||||
import java.util.BitSet;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class BinaryMessage extends BitSet
|
public class BinaryMessage extends BitSet
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(BinaryMessage.class);
|
||||||
|
private static final int[] CHARACTER_7_BIT = new int[]{0, 1, 2, 3, 4, 5, 6};
|
||||||
|
private static final int[] CHARACTER_8_BIT = new int[]{0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
private static final String UTF_8 = "UTF-8";
|
||||||
|
private static final String GB2312 = "GB2312";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logical (ie constructed) size of this bitset, despite the actual size of
|
* Logical (ie constructed) size of this bitset, despite the actual size of
|
||||||
@@ -1038,6 +1043,51 @@ public class BinaryMessage extends BitSet
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the hexadecimal text into a binary message
|
||||||
|
* @param hex text to load from
|
||||||
|
* @return loaded binary message
|
||||||
|
*/
|
||||||
|
public static BinaryMessage loadHex(String hex)
|
||||||
|
{
|
||||||
|
if(!hex.matches("[0-9A-F]*"))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Message must contain only 0-9 and A-F hexadecimal characters");
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = hex.length() / 2;
|
||||||
|
|
||||||
|
if(length * 2 < hex.length())
|
||||||
|
{
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryMessage buffer = new BinaryMessage(length * 8);
|
||||||
|
|
||||||
|
int bufferOffset = 0;
|
||||||
|
for(int x = 0; x < hex.length(); x += 2)
|
||||||
|
{
|
||||||
|
int endIndex = x + 2;
|
||||||
|
if(endIndex > hex.length())
|
||||||
|
{
|
||||||
|
endIndex--;
|
||||||
|
}
|
||||||
|
|
||||||
|
String rawHex = hex.substring(x, endIndex);
|
||||||
|
|
||||||
|
if(rawHex.length() == 1)
|
||||||
|
{
|
||||||
|
rawHex = rawHex + "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
int value = Integer.parseInt(rawHex, 16);
|
||||||
|
buffer.setByte(bufferOffset, (byte)(value & 0xFF));
|
||||||
|
bufferOffset += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Left rotates the bits between start and end indices, number of places.
|
* Left rotates the bits between start and end indices, number of places.
|
||||||
*/
|
*/
|
||||||
@@ -1133,19 +1183,121 @@ public class BinaryMessage extends BitSet
|
|||||||
this.xor(mask);
|
this.xor(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args)
|
/**
|
||||||
|
* Parse ISO-7 bit characters from the message starting at the offset.
|
||||||
|
* @param offset where to start parsing.
|
||||||
|
* @param characterCount number of characters to parse
|
||||||
|
* @return parsed message.
|
||||||
|
*/
|
||||||
|
public String parseISO7(int offset, int characterCount)
|
||||||
{
|
{
|
||||||
BinaryMessage message = new BinaryMessage(10);
|
byte[] bytes = new byte[characterCount];
|
||||||
message.set(1);
|
|
||||||
message.set(4);
|
|
||||||
message.set(5);
|
|
||||||
message.set(8);
|
|
||||||
message.set(9);
|
|
||||||
|
|
||||||
System.out.println("Hex: " + message.toHexString() + " Size:" + message.size());
|
for(int x = 0; x < characterCount; x++)
|
||||||
for(int x = 0; x < 10; x++)
|
|
||||||
{
|
{
|
||||||
System.out.println(x + ": " + message.getHex(x, x + 10));
|
bytes[x] = getByte(CHARACTER_7_BIT, x * 7 + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse ISO-8 bit characters from the message starting at the offset.
|
||||||
|
* @param offset where to start parsing.
|
||||||
|
* @param characterCount number of characters to parse
|
||||||
|
* @return parsed message.
|
||||||
|
*/
|
||||||
|
public String parseISO8(int offset, int characterCount)
|
||||||
|
{
|
||||||
|
byte[] bytes = new byte[characterCount];
|
||||||
|
|
||||||
|
for(int x = 0; x < characterCount; x++)
|
||||||
|
{
|
||||||
|
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse UTF-8 characters from the message starting at the offset.
|
||||||
|
* @param offset where to start parsing.
|
||||||
|
* @param characterCount number of characters to parse
|
||||||
|
* @return parsed message.
|
||||||
|
* @throws UnsupportedEncodingException if UTF-8 encoding is not supported.
|
||||||
|
*/
|
||||||
|
public String parseUTF8(int offset, int characterCount) throws UnsupportedEncodingException
|
||||||
|
{
|
||||||
|
byte[] bytes = new byte[characterCount];
|
||||||
|
|
||||||
|
for(int x = 0; x < characterCount; x++)
|
||||||
|
{
|
||||||
|
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(bytes, UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse GB2312 Simplified Chinese Characters from the message starting at the offset.
|
||||||
|
* @param offset where to start parsing.
|
||||||
|
* @param characterCount number of characters to parse
|
||||||
|
* @return parsed message.
|
||||||
|
* @throws UnsupportedEncodingException if GB2312 encoding is not supported.
|
||||||
|
*/
|
||||||
|
public String parseGB2312(int offset, int characterCount) throws UnsupportedEncodingException
|
||||||
|
{
|
||||||
|
int length = characterCount * 2;
|
||||||
|
byte[] bytes = new byte[length];
|
||||||
|
|
||||||
|
for(int x = 0; x < length; x++)
|
||||||
|
{
|
||||||
|
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(bytes, GB2312);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse 16-bit unicode characters from the message starting at the offset.
|
||||||
|
* @param offset where to start parsing.
|
||||||
|
* @param characterCount number of characters to parse
|
||||||
|
* @return parsed message.
|
||||||
|
*/
|
||||||
|
public String parseUnicode(int offset, int characterCount)
|
||||||
|
{
|
||||||
|
int length = characterCount * 2;
|
||||||
|
byte[] bytes = new byte[length];
|
||||||
|
|
||||||
|
for(int x = 0; x < length; x++)
|
||||||
|
{
|
||||||
|
bytes[x] = getByte(CHARACTER_8_BIT, x * 8 + offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse 4-bit Binary Coded Decimal (BCD) characters from the message starting at the offset.
|
||||||
|
* @param offset where to start parsing.
|
||||||
|
* @param characterCount number of characters to parse
|
||||||
|
* @return parsed message.
|
||||||
|
*/
|
||||||
|
public String parseBCD4(int offset, int characterCount)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
while(count < characterCount)
|
||||||
|
{
|
||||||
|
int nibble = getNibble(offset);
|
||||||
|
//This will parse digits 0-9 and anything higher will parse as hexadecimal A-F
|
||||||
|
sb.append(Integer.toHexString(nibble).toUpperCase());
|
||||||
|
offset += 4;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.channel.metadata;
|
package io.github.dsheirer.channel.metadata;
|
||||||
|
|
||||||
@@ -28,10 +27,11 @@ import io.github.dsheirer.module.decode.event.DecodeEventPanel;
|
|||||||
import io.github.dsheirer.module.decode.event.MessageActivityPanel;
|
import io.github.dsheirer.module.decode.event.MessageActivityPanel;
|
||||||
import io.github.dsheirer.playlist.PlaylistManager;
|
import io.github.dsheirer.playlist.PlaylistManager;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
|
import io.github.dsheirer.settings.SettingsManager;
|
||||||
|
import java.awt.Color;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import java.awt.Color;
|
|
||||||
|
|
||||||
public class NowPlayingPanel extends JPanel
|
public class NowPlayingPanel extends JPanel
|
||||||
{
|
{
|
||||||
@@ -45,13 +45,14 @@ public class NowPlayingPanel extends JPanel
|
|||||||
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
|
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
|
||||||
* messages, events, and spectral view.
|
* messages, events, and spectral view.
|
||||||
*/
|
*/
|
||||||
public NowPlayingPanel(PlaylistManager playlistManager, IconModel iconModel, UserPreferences userPreferences)
|
public NowPlayingPanel(PlaylistManager playlistManager, IconModel iconModel, UserPreferences userPreferences,
|
||||||
|
SettingsManager settingsManager)
|
||||||
{
|
{
|
||||||
mChannelDetailPanel = new ChannelDetailPanel(playlistManager.getChannelProcessingManager());
|
mChannelDetailPanel = new ChannelDetailPanel(playlistManager.getChannelProcessingManager());
|
||||||
mDecodeEventPanel = new DecodeEventPanel(iconModel, userPreferences, playlistManager.getAliasModel());
|
mDecodeEventPanel = new DecodeEventPanel(iconModel, userPreferences, playlistManager.getAliasModel());
|
||||||
mMessageActivityPanel = new MessageActivityPanel(userPreferences);
|
mMessageActivityPanel = new MessageActivityPanel(userPreferences);
|
||||||
mChannelMetadataPanel = new ChannelMetadataPanel(playlistManager, iconModel, userPreferences);
|
mChannelMetadataPanel = new ChannelMetadataPanel(playlistManager, iconModel, userPreferences);
|
||||||
mChannelPowerPanel = new ChannelPowerPanel(playlistManager);
|
mChannelPowerPanel = new ChannelPowerPanel(playlistManager, settingsManager);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
@@ -68,6 +69,10 @@ public class NowPlayingPanel extends JPanel
|
|||||||
tabbedPane.setFont(this.getFont());
|
tabbedPane.setFont(this.getFont());
|
||||||
tabbedPane.setForeground(Color.BLACK);
|
tabbedPane.setForeground(Color.BLACK);
|
||||||
|
|
||||||
|
//Register state change listener to toggle visibility state for channel tab to turn-on/off FFT processing
|
||||||
|
tabbedPane.addChangeListener(e -> mChannelPowerPanel.setPanelVisible(tabbedPane.getSelectedIndex() == tabbedPane
|
||||||
|
.indexOfComponent(mChannelPowerPanel)));
|
||||||
|
|
||||||
JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
|
JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
|
||||||
splitPane.setShowGripper(true);
|
splitPane.setShowGripper(true);
|
||||||
splitPane.add(mChannelMetadataPanel);
|
splitPane.add(mChannelMetadataPanel);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -32,6 +32,8 @@ import io.github.dsheirer.preference.UserPreferences;
|
|||||||
import io.github.dsheirer.settings.SettingsManager;
|
import io.github.dsheirer.settings.SettingsManager;
|
||||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
import io.github.dsheirer.source.tuner.ui.TunerViewPanel;
|
import io.github.dsheirer.source.tuner.ui.TunerViewPanel;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Dimension;
|
||||||
import jiconfont.icons.font_awesome.FontAwesome;
|
import jiconfont.icons.font_awesome.FontAwesome;
|
||||||
import jiconfont.swing.IconFontSwing;
|
import jiconfont.swing.IconFontSwing;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
@@ -41,8 +43,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Dimension;
|
|
||||||
|
|
||||||
public class ControllerPanel extends JPanel
|
public class ControllerPanel extends JPanel
|
||||||
{
|
{
|
||||||
@@ -63,7 +63,7 @@ public class ControllerPanel extends JPanel
|
|||||||
{
|
{
|
||||||
mAudioPanel = new AudioPanel(iconModel, userPreferences, settingsManager, audioPlaybackManager,
|
mAudioPanel = new AudioPanel(iconModel, userPreferences, settingsManager, audioPlaybackManager,
|
||||||
playlistManager.getAliasModel());
|
playlistManager.getAliasModel());
|
||||||
mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconModel, userPreferences);
|
mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconModel, userPreferences, settingsManager);
|
||||||
mMapPanel = new MapPanel(mapService, playlistManager.getAliasModel(), iconModel, settingsManager);
|
mMapPanel = new MapPanel(mapService, playlistManager.getAliasModel(), iconModel, settingsManager);
|
||||||
mTunerManagerPanel = new TunerViewPanel(tunerManager, userPreferences);
|
mTunerManagerPanel = new TunerViewPanel(tunerManager, userPreferences);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,22 +14,13 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.controller.channel;
|
package io.github.dsheirer.controller.channel;
|
||||||
|
|
||||||
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import net.miginfocom.swing.MigLayout;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JFrame;
|
|
||||||
import javax.swing.JLabel;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JScrollPane;
|
|
||||||
import javax.swing.JTable;
|
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.EventQueue;
|
import java.awt.EventQueue;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
@@ -41,12 +31,21 @@ import java.util.List;
|
|||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JTable;
|
||||||
|
|
||||||
public class ChannelAutoStartFrame extends JFrame
|
public class ChannelAutoStartFrame extends JFrame
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelAutoStartFrame.class);
|
private final static Logger mLog = LoggerFactory.getLogger(ChannelAutoStartFrame.class);
|
||||||
|
|
||||||
private static final int COUNTDOWN_SECONDS = 10;
|
|
||||||
private Listener<ChannelEvent> mChannelEventListener;
|
private Listener<ChannelEvent> mChannelEventListener;
|
||||||
private List<Channel> mChannels;
|
private List<Channel> mChannels;
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@ public class ChannelAutoStartFrame extends JFrame
|
|||||||
private JButton mCancelButton;
|
private JButton mCancelButton;
|
||||||
private JTable mChannelTable;
|
private JTable mChannelTable;
|
||||||
private AtomicBoolean mChannelsStarted = new AtomicBoolean();
|
private AtomicBoolean mChannelsStarted = new AtomicBoolean();
|
||||||
|
private int mAutoStartTimeoutSeconds;
|
||||||
private ScheduledFuture<?> mTimerFuture;
|
private ScheduledFuture<?> mTimerFuture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,10 +65,12 @@ public class ChannelAutoStartFrame extends JFrame
|
|||||||
* @param listener to receive channel start/enable request(s)
|
* @param listener to receive channel start/enable request(s)
|
||||||
* @param channels to auto-start
|
* @param channels to auto-start
|
||||||
*/
|
*/
|
||||||
public ChannelAutoStartFrame(Listener<ChannelEvent> listener, List<Channel> channels)
|
public ChannelAutoStartFrame(Listener<ChannelEvent> listener, List<Channel> channels, UserPreferences userPreferences)
|
||||||
{
|
{
|
||||||
mChannelEventListener = listener;
|
mChannelEventListener = listener;
|
||||||
mChannels = channels;
|
mChannels = channels;
|
||||||
|
mAutoStartTimeoutSeconds = userPreferences.getApplicationPreference().getChannelAutoStartTimeout();
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
EventQueue.invokeLater(() -> {
|
EventQueue.invokeLater(() -> {
|
||||||
@@ -100,7 +101,7 @@ public class ChannelAutoStartFrame extends JFrame
|
|||||||
"[][][grow,fill][]"));
|
"[][][grow,fill][]"));
|
||||||
panel.add(new JLabel("The following channels will be automatically"), "span");
|
panel.add(new JLabel("The following channels will be automatically"), "span");
|
||||||
|
|
||||||
mCountdownLabel = new JLabel(getCountdownText(COUNTDOWN_SECONDS));
|
mCountdownLabel = new JLabel(getCountdownText(mAutoStartTimeoutSeconds));
|
||||||
panel.add(mCountdownLabel, "span");
|
panel.add(mCountdownLabel, "span");
|
||||||
|
|
||||||
panel.add(new JScrollPane(getChannelTable()), "span");
|
panel.add(new JScrollPane(getChannelTable()), "span");
|
||||||
@@ -220,7 +221,7 @@ public class ChannelAutoStartFrame extends JFrame
|
|||||||
*/
|
*/
|
||||||
public class CountdownTimer implements Runnable
|
public class CountdownTimer implements Runnable
|
||||||
{
|
{
|
||||||
private int mCount = COUNTDOWN_SECONDS;
|
private int mCount = mAutoStartTimeoutSeconds;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -623,30 +623,27 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
|
|||||||
Platform.runLater(() -> channel.setProcessing(false));
|
Platform.runLater(() -> channel.setProcessing(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
//Since the processing chain's source will block on stop(), throw it to the thread pool to run
|
try
|
||||||
ThreadPool.CACHED.submit(() -> {
|
{
|
||||||
try
|
processingChain.stop();
|
||||||
{
|
processingChain.removeEventLoggingModules();
|
||||||
processingChain.stop();
|
processingChain.removeRecordingModules();
|
||||||
processingChain.removeEventLoggingModules();
|
|
||||||
processingChain.removeRecordingModules();
|
|
||||||
|
|
||||||
//Deregister channel from receive frequency correction events to show in the spectral display (hack!)
|
//Deregister channel from receive frequency correction events to show in the spectral display (hack!)
|
||||||
processingChain.removeFrequencyChangeListener(channel);
|
processingChain.removeFrequencyChangeListener(channel);
|
||||||
channel.resetFrequencyCorrection();
|
channel.resetFrequencyCorrection();
|
||||||
|
|
||||||
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP));
|
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, ChannelEvent.Event.NOTIFICATION_PROCESSING_STOP));
|
||||||
mChannelEventBroadcaster.removeListener(processingChain);
|
mChannelEventBroadcaster.removeListener(processingChain);
|
||||||
|
|
||||||
//Unregister for event bus requests and notifications
|
//Unregister for event bus requests and notifications
|
||||||
processingChain.getEventBus().unregister(ChannelProcessingManager.this);
|
processingChain.getEventBus().unregister(ChannelProcessingManager.this);
|
||||||
processingChain.dispose();
|
processingChain.dispose();
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
mLog.error("Error during shutdown of processing chain for channel [" + channel.getName() + "}", e);
|
mLog.error("Error during shutdown of processing chain for channel [" + channel.getName() + "}", e);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2018 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.controller.channel;
|
package io.github.dsheirer.controller.channel;
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ public class ChannelSelectionManager implements Listener<ChannelEvent>
|
|||||||
public ChannelSelectionManager(ChannelModel channelModel)
|
public ChannelSelectionManager(ChannelModel channelModel)
|
||||||
{
|
{
|
||||||
mChannelEventListener = channelModel;
|
mChannelEventListener = channelModel;
|
||||||
channelModel.addListener(this::receive);
|
channelModel.addListener(ChannelSelectionManager.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -122,6 +122,11 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<In
|
|||||||
mChannels.add(polyphaseChannelSource);
|
mChannels.add(polyphaseChannelSource);
|
||||||
mSourceChangeBroadcaster.addListener(polyphaseChannelSource.getSourceEventListener());
|
mSourceChangeBroadcaster.addListener(polyphaseChannelSource.getSourceEventListener());
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("Error adding polyphase channel source - " + (polyphaseChannelSource == null ? "source is null" :
|
||||||
|
"channel source is already added to this channelizer"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -136,6 +141,11 @@ public abstract class AbstractComplexPolyphaseChannelizer implements Listener<In
|
|||||||
mChannels.remove(polyphaseChannelSource);
|
mChannels.remove(polyphaseChannelSource);
|
||||||
mSourceChangeBroadcaster.removeListener(polyphaseChannelSource.getSourceEventListener());
|
mSourceChangeBroadcaster.removeListener(polyphaseChannelSource.getSourceEventListener());
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("Error removing polyphase channel source - " + (polyphaseChannelSource == null ? "source is null" :
|
||||||
|
"channel source was not previously added to this channelizer"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,31 +1,34 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* *********************************************************************************************************************
|
* *****************************************************************************
|
||||||
* sdr-trunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
* it under the terms of the GNU General Public License as published by
|
||||||
* later version.
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
* This program is distributed in the hope that it will be useful,
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along with this program.
|
* You should have received a copy of the GNU General Public License
|
||||||
* If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *********************************************************************************************************************
|
* ****************************************************************************
|
||||||
******************************************************************************/
|
*/
|
||||||
package io.github.dsheirer.dsp.filter.channelizer;
|
package io.github.dsheirer.dsp.filter.channelizer;
|
||||||
|
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
import org.apache.commons.math3.util.FastMath;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ChannelCalculator
|
public class ChannelCalculator
|
||||||
{
|
{
|
||||||
|
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelCalculator.class);
|
private final static Logger mLog = LoggerFactory.getLogger(ChannelCalculator.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -546,13 +549,13 @@ public class ChannelCalculator
|
|||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("Channel Calculator Settings");
|
sb.append("Channel Calculator | Tuner SR:").append(FREQUENCY_FORMAT.format(getSampleRate() / 1E6D));
|
||||||
sb.append(" Channels:").append(getChannelCount());
|
sb.append(" CF:").append(FREQUENCY_FORMAT.format(getCenterFrequency() / 1E6D));
|
||||||
sb.append(" Sample Rate:").append(getSampleRate());
|
sb.append(" MIN:").append(FREQUENCY_FORMAT.format(getMinimumFrequency() / 1E6D));
|
||||||
sb.append(" Channel Bandwidth:").append(getChannelBandwidth());
|
sb.append(" MAX:").append(FREQUENCY_FORMAT.format(getMaximumFrequency() / 1E6D));
|
||||||
sb.append(" Channel Rate:").append(getChannelSampleRate());
|
sb.append(" | Channel COUNT:").append(getChannelCount());
|
||||||
sb.append(" Min:").append(getMinimumFrequency());
|
sb.append(" BW:").append(FREQUENCY_FORMAT.format(getChannelBandwidth() / 1E6D));
|
||||||
sb.append(" Max:").append(getMaximumFrequency());
|
sb.append(" SR:").append(FREQUENCY_FORMAT.format(getChannelSampleRate() / 1E6D));
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,7 +24,6 @@ import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
|||||||
import io.github.dsheirer.util.Dispatcher;
|
import io.github.dsheirer.util.Dispatcher;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
import org.apache.commons.math3.util.FastMath;
|
||||||
import org.jtransforms.fft.FloatFFT_1D;
|
import org.jtransforms.fft.FloatFFT_1D;
|
||||||
@@ -69,8 +68,8 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
|||||||
*/
|
*/
|
||||||
private static final int PROCESSED_CHANNEL_RESULTS_THRESHOLD = 1024;
|
private static final int PROCESSED_CHANNEL_RESULTS_THRESHOLD = 1024;
|
||||||
|
|
||||||
//Sized at 152 buffers a second where max = 5 seconds
|
//Sized to process 1/20th batch of 152 buffers a second, at 40 times per second
|
||||||
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(5 * 152);
|
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(152 / 20, 25);
|
||||||
private FloatFFT_1D mFFT;
|
private FloatFFT_1D mFFT;
|
||||||
private float[] mInlineSamples;
|
private float[] mInlineSamples;
|
||||||
private float[] mInlineFilter;
|
private float[] mInlineFilter;
|
||||||
@@ -407,25 +406,32 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
|||||||
*/
|
*/
|
||||||
public class IFFTProcessorDispatcher extends Dispatcher<List<float[]>>
|
public class IFFTProcessorDispatcher extends Dispatcher<List<float[]>>
|
||||||
{
|
{
|
||||||
public IFFTProcessorDispatcher(int maximumSize)
|
public IFFTProcessorDispatcher(int batchSize, long interval)
|
||||||
{
|
{
|
||||||
super(maximumSize, "sdrtrunk polyphase ifft processor", Collections.emptyList());
|
super("sdrtrunk polyphase ifft processor", batchSize, interval);
|
||||||
|
|
||||||
//We create a listener interface to receive the batched channel results arrays from the scheduled thread pool
|
//We create a listener interface to receive the batched channel results arrays from the scheduled thread pool
|
||||||
//dispatcher thread that is part of this continuous buffer processor. We perform an IFFT on each
|
//dispatcher thread that is part of this continuous buffer processor. We perform an IFFT on each
|
||||||
//channel results array contained in each results buffer and then dispatch the buffer
|
//channel results array contained in each results buffer and then dispatch the buffer
|
||||||
//so that it can be distributed to each channel listener.
|
//so that it can be distributed to each channel listener.
|
||||||
setListener(list -> {
|
setListener(list -> {
|
||||||
List<float[]> processedChannelResults = new ArrayList<>();
|
try
|
||||||
|
|
||||||
for(float[] channelResults: list)
|
|
||||||
{
|
{
|
||||||
//Rotate each of the channels to the correct phase using the IFFT
|
List<float[]> processedChannelResults = new ArrayList<>();
|
||||||
mFFT.complexInverse(channelResults, true);
|
|
||||||
processedChannelResults.add(channelResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(processedChannelResults);
|
for(float[] channelResults: list)
|
||||||
|
{
|
||||||
|
//Rotate each of the channels to the correct phase using the IFFT
|
||||||
|
mFFT.complexInverse(channelResults, true);
|
||||||
|
processedChannelResults.add(channelResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(processedChannelResults);
|
||||||
|
}
|
||||||
|
catch(Throwable t)
|
||||||
|
{
|
||||||
|
mLog.error("Error during IFFT and dispatch of processed channel results", t);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,7 +20,6 @@ package io.github.dsheirer.dsp.filter.channelizer;
|
|||||||
|
|
||||||
import io.github.dsheirer.buffer.INativeBuffer;
|
import io.github.dsheirer.buffer.INativeBuffer;
|
||||||
import io.github.dsheirer.buffer.INativeBufferProvider;
|
import io.github.dsheirer.buffer.INativeBufferProvider;
|
||||||
import io.github.dsheirer.buffer.NativeBufferPoisonPill;
|
|
||||||
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
|
import io.github.dsheirer.controller.channel.event.ChannelStopProcessingRequest;
|
||||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
import io.github.dsheirer.eventbus.MyEventBus;
|
||||||
@@ -28,13 +27,13 @@ import io.github.dsheirer.sample.Broadcaster;
|
|||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||||
import io.github.dsheirer.source.ISourceEventProcessor;
|
import io.github.dsheirer.source.ISourceEventProcessor;
|
||||||
import io.github.dsheirer.source.Source;
|
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
import io.github.dsheirer.source.SourceException;
|
import io.github.dsheirer.source.SourceException;
|
||||||
import io.github.dsheirer.source.tuner.TunerController;
|
import io.github.dsheirer.source.tuner.TunerController;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||||
import io.github.dsheirer.util.Dispatcher;
|
import io.github.dsheirer.util.Dispatcher;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@@ -65,6 +64,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
*/
|
*/
|
||||||
public class PolyphaseChannelManager implements ISourceEventProcessor
|
public class PolyphaseChannelManager implements ISourceEventProcessor
|
||||||
{
|
{
|
||||||
|
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
private final static Logger mLog = LoggerFactory.getLogger(PolyphaseChannelManager.class);
|
||||||
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
|
private static final double MINIMUM_CHANNEL_BANDWIDTH = 25000.0;
|
||||||
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
private static final double CHANNEL_OVERSAMPLING = 2.0;
|
||||||
@@ -108,8 +108,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
|
|
||||||
mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING);
|
mChannelCalculator = new ChannelCalculator(sampleRate, channelCount, frequency, CHANNEL_OVERSAMPLING);
|
||||||
mBufferDispatcher = new Dispatcher(500, "sdrtrunk polyphase buffer processor",
|
mBufferDispatcher = new Dispatcher("sdrtrunk polyphase buffer processor", 50, 10);
|
||||||
new NativeBufferPoisonPill());
|
|
||||||
mBufferDispatcher.setListener(mNativeBufferReceiver);
|
mBufferDispatcher.setListener(mNativeBufferReceiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +122,34 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
this(tunerController, tunerController.getFrequency(), tunerController.getSampleRate());
|
this(tunerController, tunerController.getFrequency(), tunerController.getSampleRate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a description of the state of this manager.
|
||||||
|
*/
|
||||||
|
public String getStateDescription()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Polyphase Channel Manager Providing [").append(mChannelSources.size()).append("] Channels");
|
||||||
|
sb.append("\n\t").append(mChannelCalculator);
|
||||||
|
for(PolyphaseChannelSource pcs: mChannelSources)
|
||||||
|
{
|
||||||
|
List<Integer> indexes = pcs.getOutputProcessorIndexes();
|
||||||
|
double sampleRate = pcs.getSampleRate();
|
||||||
|
long indexCenterFrequency = pcs.getIndexCenterFrequency();
|
||||||
|
long appliedFrequencyOffset = pcs.getFrequencyOffset();
|
||||||
|
long requestedCenterFrequency = pcs.getFrequency();
|
||||||
|
|
||||||
|
sb.append("\n\tPolyphase | Tuner SR:").append(FREQUENCY_FORMAT.format(pcs.getTunerSampleRate() / 1E6d));
|
||||||
|
sb.append(" CF:").append(FREQUENCY_FORMAT.format(pcs.getTunerCenterFrequency() / 1E6d));
|
||||||
|
sb.append(" BW: ").append(FREQUENCY_FORMAT.format(sampleRate / 1E6d));
|
||||||
|
sb.append(" | Channel CF: ").append(FREQUENCY_FORMAT.format(indexCenterFrequency / 1E6d));
|
||||||
|
sb.append(" REQUESTED CF: ").append(FREQUENCY_FORMAT.format(requestedCenterFrequency / 1E6d));
|
||||||
|
sb.append(" MIXER:").append(FREQUENCY_FORMAT.format(appliedFrequencyOffset / 1E6d));
|
||||||
|
sb.append(" | Polyphase Indices: ").append(indexes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public void stopAllChannels()
|
public void stopAllChannels()
|
||||||
{
|
{
|
||||||
mRunning = false;
|
mRunning = false;
|
||||||
@@ -147,14 +174,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Current channel sample rate which is (2 * channel bandwidth).
|
|
||||||
*/
|
|
||||||
public double getChannelSampleRate()
|
|
||||||
{
|
|
||||||
return mChannelCalculator.getChannelSampleRate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current channel bandwidth/spacing.
|
* Current channel bandwidth/spacing.
|
||||||
*/
|
*/
|
||||||
@@ -278,7 +297,7 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
//no-op
|
//no-op
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
mLog.info("Unrecognized source event: " + sourceEvent.toString());
|
mLog.info("Unrecognized source event: " + sourceEvent);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -409,9 +428,10 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case REQUEST_STOP_SAMPLE_STREAM:
|
case REQUEST_STOP_SAMPLE_STREAM:
|
||||||
if(sourceEvent.hasSource() && sourceEvent.getSource() instanceof PolyphaseChannelSource)
|
if(sourceEvent.hasSource() && sourceEvent.getSource() instanceof PolyphaseChannelSource channelSource)
|
||||||
{
|
{
|
||||||
stopChannelSource((PolyphaseChannelSource)sourceEvent.getSource());
|
stopChannelSource(channelSource);
|
||||||
|
channelSource.dispose();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -419,17 +439,6 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
(sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "null source"));
|
(sourceEvent.hasSource() ? sourceEvent.getSource().getClass() : "null source"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case REQUEST_SOURCE_DISPOSE:
|
|
||||||
Source source = sourceEvent.getSource();
|
|
||||||
|
|
||||||
if(source instanceof PolyphaseChannelSource)
|
|
||||||
{
|
|
||||||
source.dispose();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case NOTIFICATION_FREQUENCY_CORRECTION_CHANGE:
|
|
||||||
//ignore
|
|
||||||
break;
|
|
||||||
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
|
case NOTIFICATION_MEASURED_FREQUENCY_ERROR_SYNC_LOCKED:
|
||||||
//Rebroadcast so that the tuner source can process this event
|
//Rebroadcast so that the tuner source can process this event
|
||||||
mSourceEventBroadcaster.broadcast(sourceEvent);
|
mSourceEventBroadcaster.broadcast(sourceEvent);
|
||||||
@@ -445,16 +454,17 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
/**
|
/**
|
||||||
* Processes the incoming buffer stream from the provider and transfers the buffers to the polyphase channelizer.
|
* Processes the incoming buffer stream from the provider and transfers the buffers to the polyphase channelizer.
|
||||||
*
|
*
|
||||||
* This monitor incorporates a queue for receiving source events that affect configuration of the channelizer so
|
* This monitor incorporates a source event handler that queues a center frequency update so that it can be
|
||||||
* that they can be processed on the buffer processor calling thread, avoiding unnecessary locks on the channelizer
|
* handled on the buffer processing thread, avoiding having to lock on the output processor thread. Since we
|
||||||
* and/or the channel sources and output processors.
|
* anticipate that these two threads will contend for access to this update required flag, we use an update lock
|
||||||
|
* to protect access to the flag.
|
||||||
*/
|
*/
|
||||||
public class NativeBufferReceiver implements Listener<INativeBuffer>
|
public class NativeBufferReceiver implements Listener<INativeBuffer>
|
||||||
{
|
{
|
||||||
private boolean mOutputProcessorUpdateRequired = false;
|
private boolean mOutputProcessorUpdateRequired = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queues the source event for deferred execution on the buffer processing thread.
|
* Processes tuner center frequency change source events to flag when output processors need updating.
|
||||||
* @param event that affects configuration of the channelizer (frequency or sample rate change events)
|
* @param event that affects configuration of the channelizer (frequency or sample rate change events)
|
||||||
*/
|
*/
|
||||||
public void receive(SourceEvent event)
|
public void receive(SourceEvent event)
|
||||||
@@ -463,36 +473,49 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
|
|
||||||
if(mChannelCalculator.getCenterFrequency() != frequency)
|
if(mChannelCalculator.getCenterFrequency() != frequency)
|
||||||
{
|
{
|
||||||
|
//Update the channel calculator frequency so that it's ready when the output processor update occurs
|
||||||
mChannelCalculator.setCenterFrequency(frequency);
|
mChannelCalculator.setCenterFrequency(frequency);
|
||||||
mOutputProcessorUpdateRequired = true;
|
mOutputProcessorUpdateRequired = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process native buffer streams and update polyphase output channels when the parent tuner center
|
||||||
|
* frequency changes.
|
||||||
|
* @param nativeBuffer of sample to process.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void receive(INativeBuffer nativeBuffer)
|
public void receive(INativeBuffer nativeBuffer)
|
||||||
{
|
{
|
||||||
try
|
if(mOutputProcessorUpdateRequired)
|
||||||
{
|
{
|
||||||
if(mOutputProcessorUpdateRequired)
|
try
|
||||||
{
|
{
|
||||||
updateOutputProcessors();
|
updateOutputProcessors();
|
||||||
mOutputProcessorUpdateRequired = false;
|
|
||||||
}
|
}
|
||||||
|
catch(Exception e)
|
||||||
if(mPolyphaseChannelizer != null)
|
|
||||||
{
|
{
|
||||||
Iterator<InterleavedComplexSamples> iterator = nativeBuffer.iteratorInterleaved();
|
mLog.error("Error updating polyphase channel output processors");
|
||||||
|
}
|
||||||
|
mOutputProcessorUpdateRequired = false;
|
||||||
|
}
|
||||||
|
|
||||||
while(iterator.hasNext())
|
if(mPolyphaseChannelizer != null)
|
||||||
|
{
|
||||||
|
Iterator<InterleavedComplexSamples> iterator = nativeBuffer.iteratorInterleaved();
|
||||||
|
|
||||||
|
while(iterator.hasNext())
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
mPolyphaseChannelizer.receive(iterator.next());
|
mPolyphaseChannelizer.receive(iterator.next());
|
||||||
}
|
}
|
||||||
|
catch(Throwable throwable)
|
||||||
|
{
|
||||||
|
mLog.error("Error", throwable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(Throwable throwable)
|
|
||||||
{
|
|
||||||
mLog.error("Error", throwable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,11 +25,10 @@ import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
|||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
import io.github.dsheirer.source.tuner.channel.StreamProcessorWithHeartbeat;
|
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||||
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -42,11 +41,13 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
{
|
{
|
||||||
private Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
|
private Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
|
||||||
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
|
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
|
||||||
private StreamProcessorWithHeartbeat<ComplexSamples> mStreamHeartbeatProcessor;
|
private Listener<ComplexSamples> mSamplesListener;
|
||||||
private double mChannelSampleRate;
|
private double mChannelSampleRate;
|
||||||
private long mIndexCenterFrequency;
|
private long mIndexCenterFrequency;
|
||||||
private long mChannelFrequencyCorrection;
|
private List<Integer> mOutputProcessorIndexes = new ArrayList<>();
|
||||||
private ReentrantLock mOutputProcessorLock = new ReentrantLock();
|
private double mTunerSampleRate;
|
||||||
|
private double mTunerCenterFrequency;
|
||||||
|
private PendingOutputProcessorUpdate mPendingOutputProcessorUpdate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -62,8 +63,42 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
{
|
{
|
||||||
super(producerSourceEventListener, tunerChannel);
|
super(producerSourceEventListener, tunerChannel);
|
||||||
mChannelSampleRate = channelCalculator.getChannelSampleRate();
|
mChannelSampleRate = channelCalculator.getChannelSampleRate();
|
||||||
mStreamHeartbeatProcessor = new StreamProcessorWithHeartbeat<>(getHeartbeatManager(), HEARTBEAT_INTERVAL_MS);
|
doUpdateOutputProcessor(channelCalculator, filterManager);
|
||||||
updateOutputProcessor(channelCalculator, filterManager);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of the state or configuration of the output processor.
|
||||||
|
*/
|
||||||
|
public String getStateDescription()
|
||||||
|
{
|
||||||
|
return mPolyphaseChannelOutputProcessor.getStateDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current output processor indexes.
|
||||||
|
* @return indexes
|
||||||
|
*/
|
||||||
|
public List<Integer> getOutputProcessorIndexes()
|
||||||
|
{
|
||||||
|
return mOutputProcessorIndexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sample rate or bandwidth of the tuner providing input to the channelizer.
|
||||||
|
* @return sample rate
|
||||||
|
*/
|
||||||
|
public double getTunerSampleRate()
|
||||||
|
{
|
||||||
|
return mTunerSampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Center tuned frequency of the tuner providing input to the channelizer.
|
||||||
|
* @return center frequency
|
||||||
|
*/
|
||||||
|
public double getTunerCenterFrequency()
|
||||||
|
{
|
||||||
|
return mTunerCenterFrequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,8 +106,6 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
{
|
{
|
||||||
super.start();
|
super.start();
|
||||||
|
|
||||||
mStreamHeartbeatProcessor.start();
|
|
||||||
|
|
||||||
if(mPolyphaseChannelOutputProcessor != null)
|
if(mPolyphaseChannelOutputProcessor != null)
|
||||||
{
|
{
|
||||||
mPolyphaseChannelOutputProcessor.start();
|
mPolyphaseChannelOutputProcessor.start();
|
||||||
@@ -88,8 +121,6 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
{
|
{
|
||||||
mPolyphaseChannelOutputProcessor.stop();
|
mPolyphaseChannelOutputProcessor.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
mStreamHeartbeatProcessor.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,107 +129,129 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
@Override
|
@Override
|
||||||
public void setListener(final Listener<ComplexSamples> listener)
|
public void setListener(final Listener<ComplexSamples> listener)
|
||||||
{
|
{
|
||||||
mStreamHeartbeatProcessor.setListener(listener);
|
mSamplesListener = listener;
|
||||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receive(ComplexSamples complexSamples)
|
public void receive(ComplexSamples complexSamples)
|
||||||
{
|
{
|
||||||
mStreamHeartbeatProcessor.receive(complexSamples);
|
if(mSamplesListener != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mSamplesListener.receive(complexSamples);
|
||||||
|
}
|
||||||
|
catch(Throwable t)
|
||||||
|
{
|
||||||
|
mLog.error("Error dispatching complex samples to listener [" + mSamplesListener.getClass() + "]", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the output processor whenever the source tuner's center frequency changes.
|
* Queues a request to update the output processor whenever the source tuner's center frequency changes.
|
||||||
* @param channelCalculator providing access to updated tuner center frequency, sample rate, etc.
|
* @param channelCalculator providing access to updated tuner center frequency, sample rate, etc.
|
||||||
* @param filterManager for designing and caching synthesis filters
|
* @param filterManager for designing and caching synthesis filters
|
||||||
*/
|
*/
|
||||||
public void updateOutputProcessor(ChannelCalculator channelCalculator, SynthesisFilterManager filterManager) throws IllegalArgumentException
|
public void updateOutputProcessor(ChannelCalculator channelCalculator, SynthesisFilterManager filterManager)
|
||||||
|
throws IllegalArgumentException
|
||||||
|
{
|
||||||
|
mPendingOutputProcessorUpdate = new PendingOutputProcessorUpdate(channelCalculator, filterManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the output processor update. This method must be invoked on the channel results calling thread to
|
||||||
|
* ensure we don't have thread contention on the output processor.
|
||||||
|
* @param channelCalculator for updates
|
||||||
|
* @param filterManager for updates
|
||||||
|
* @throws IllegalArgumentException if there's an issue.
|
||||||
|
*/
|
||||||
|
public void doUpdateOutputProcessor(ChannelCalculator channelCalculator, SynthesisFilterManager filterManager)
|
||||||
|
throws IllegalArgumentException
|
||||||
{
|
{
|
||||||
String errorMessage = null;
|
String errorMessage = null;
|
||||||
|
|
||||||
mOutputProcessorLock.lock();
|
//If a change in sample rate or center frequency makes this channel no longer viable, then the channel
|
||||||
|
//calculator will throw an IllegalArgException ... handled below
|
||||||
|
List<Integer> indexes = channelCalculator.getChannelIndexes(getTunerChannel());
|
||||||
|
|
||||||
try
|
mOutputProcessorIndexes.clear();
|
||||||
|
mOutputProcessorIndexes.addAll(indexes);
|
||||||
|
mTunerCenterFrequency = channelCalculator.getCenterFrequency();
|
||||||
|
mTunerSampleRate = channelCalculator.getSampleRate();
|
||||||
|
|
||||||
|
//The provided channels are necessarily aligned to the center frequency that this source is providing and an
|
||||||
|
//oscillator will mix the provided channels to bring the desired center frequency to baseband.
|
||||||
|
setFrequency(channelCalculator.getCenterFrequencyForIndexes(indexes));
|
||||||
|
|
||||||
|
if(mPolyphaseChannelOutputProcessor != null && mPolyphaseChannelOutputProcessor.getInputChannelCount() == indexes.size())
|
||||||
{
|
{
|
||||||
//If a change in sample rate or center frequency makes this channel no longer viable, then the channel
|
mPolyphaseChannelOutputProcessor.setPolyphaseChannelIndices(indexes);
|
||||||
//calculator will throw an IllegalArgException ... handled below
|
|
||||||
List<Integer> indexes = channelCalculator.getChannelIndexes(getTunerChannel());
|
|
||||||
|
|
||||||
//The provided channels are necessarily aligned to the center frequency that this source is providing and an
|
if(indexes.size() > 1)
|
||||||
//oscillator will mix the provided channels to bring the desired center frequency to baseband.
|
|
||||||
setFrequency(channelCalculator.getCenterFrequencyForIndexes(indexes));
|
|
||||||
|
|
||||||
if(mPolyphaseChannelOutputProcessor != null && mPolyphaseChannelOutputProcessor.getInputChannelCount() == indexes.size())
|
|
||||||
{
|
{
|
||||||
mPolyphaseChannelOutputProcessor.setPolyphaseChannelIndices(indexes);
|
try
|
||||||
|
|
||||||
if(indexes.size() > 1)
|
|
||||||
{
|
{
|
||||||
|
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
|
||||||
|
channelCalculator.getChannelBandwidth(), indexes.size());
|
||||||
|
mPolyphaseChannelOutputProcessor.setSynthesisFilter(filter);
|
||||||
|
}
|
||||||
|
catch(FilterDesignException fde)
|
||||||
|
{
|
||||||
|
mLog.error("Error creating an updated synthesis filter for the channel output processor");
|
||||||
|
errorMessage ="Cannot update output processor - unable to design synthesis filter for [" +
|
||||||
|
indexes.size() + "] channel indices - channel sample rate [" + channelCalculator.getChannelSampleRate() +
|
||||||
|
"] channel bandwidth [" + channelCalculator.getChannelBandwidth() + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||||
|
}
|
||||||
|
else //Create a new output processor.
|
||||||
|
{
|
||||||
|
if(mPolyphaseChannelOutputProcessor != null)
|
||||||
|
{
|
||||||
|
mPolyphaseChannelOutputProcessor.setListener(null);
|
||||||
|
mPolyphaseChannelOutputProcessor.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
mPolyphaseChannelOutputProcessor = null;
|
||||||
|
|
||||||
|
switch(indexes.size())
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
mPolyphaseChannelOutputProcessor = new OneChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
|
||||||
|
indexes, channelCalculator.getChannelCount(), getHeartbeatManager());
|
||||||
|
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||||
|
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||||
|
mPolyphaseChannelOutputProcessor.start();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
float[] filter = filterManager.getFilter(channelCalculator.getSampleRate(),
|
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
|
||||||
channelCalculator.getChannelBandwidth(), indexes.size());
|
channelCalculator.getChannelBandwidth(), 2);
|
||||||
mPolyphaseChannelOutputProcessor.setSynthesisFilter(filter);
|
mPolyphaseChannelOutputProcessor = new TwoChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
|
||||||
}
|
indexes, filter, channelCalculator.getChannelCount(), getHeartbeatManager());
|
||||||
catch(FilterDesignException fde)
|
|
||||||
{
|
|
||||||
mLog.error("Error creating an updated synthesis filter for the channel output processor");
|
|
||||||
errorMessage ="Cannot update output processor - unable to design synthesis filter for [" +
|
|
||||||
indexes.size() + "] channel indices";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
|
||||||
}
|
|
||||||
else //Create a new output processor.
|
|
||||||
{
|
|
||||||
if(mPolyphaseChannelOutputProcessor != null)
|
|
||||||
{
|
|
||||||
mPolyphaseChannelOutputProcessor.setListener(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
mPolyphaseChannelOutputProcessor = null;
|
|
||||||
|
|
||||||
switch(indexes.size())
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
mPolyphaseChannelOutputProcessor = new OneChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
|
|
||||||
indexes, channelCalculator.getChannelCount());
|
|
||||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
||||||
mPolyphaseChannelOutputProcessor.start();
|
mPolyphaseChannelOutputProcessor.start();
|
||||||
break;
|
}
|
||||||
case 2:
|
catch(FilterDesignException fde)
|
||||||
try
|
{
|
||||||
{
|
errorMessage = "Cannot create new output processor - unable to design synthesis filter for [" +
|
||||||
float[] filter = filterManager.getFilter(channelCalculator.getChannelSampleRate(),
|
indexes.size() + "] channel indices";
|
||||||
channelCalculator.getChannelBandwidth(), 2);
|
mLog.error("Error creating a synthesis filter for a new channel output processor");
|
||||||
mPolyphaseChannelOutputProcessor = new TwoChannelOutputProcessor(channelCalculator.getChannelSampleRate(),
|
}
|
||||||
indexes, filter, channelCalculator.getChannelCount());
|
break;
|
||||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
default:
|
||||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
mLog.error("Request to create an output processor for unexpected channel index size:" + indexes.size());
|
||||||
mPolyphaseChannelOutputProcessor.start();
|
mLog.info(channelCalculator.toString());
|
||||||
}
|
errorMessage = "Unable to create new channel output processor - unexpected channel index size: " +
|
||||||
catch(FilterDesignException fde)
|
indexes.size();
|
||||||
{
|
|
||||||
errorMessage = "Cannot create new output processor - unable to design synthesis filter for [" +
|
|
||||||
indexes.size() + "] channel indices";
|
|
||||||
mLog.error("Error creating a synthesis filter for a new channel output processor");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
mLog.error("Request to create an output processor for unexpected channel index size:" + indexes.size());
|
|
||||||
mLog.info(channelCalculator.toString());
|
|
||||||
errorMessage = "Unable to create new channel output processor - unexpected channel index size: " +
|
|
||||||
indexes.size();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
mOutputProcessorLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Unlikely, but if we had an error designing a synthesis filter, throw an exception
|
//Unlikely, but if we had an error designing a synthesis filter, throw an exception
|
||||||
if(errorMessage != null)
|
if(errorMessage != null)
|
||||||
@@ -217,7 +270,13 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
*/
|
*/
|
||||||
public void receiveChannelResults(List<float[]> channelResultsList, long currentSamplesTimestamp)
|
public void receiveChannelResults(List<float[]> channelResultsList, long currentSamplesTimestamp)
|
||||||
{
|
{
|
||||||
mOutputProcessorLock.lock();
|
if(mPendingOutputProcessorUpdate != null)
|
||||||
|
{
|
||||||
|
ChannelCalculator channelCalculator = mPendingOutputProcessorUpdate.getChannelCalculator();
|
||||||
|
SynthesisFilterManager filterManager = mPendingOutputProcessorUpdate.getSynthesisFilterManager();
|
||||||
|
mPendingOutputProcessorUpdate = null;
|
||||||
|
doUpdateOutputProcessor(channelCalculator, filterManager);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -226,9 +285,9 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsList, currentSamplesTimestamp);
|
mPolyphaseChannelOutputProcessor.receiveChannelResults(channelResultsList, currentSamplesTimestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
mOutputProcessorLock.unlock();
|
mLog.error("Error processing channel results", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,59 +322,32 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getChannelFrequencyCorrection()
|
|
||||||
{
|
|
||||||
return mChannelFrequencyCorrection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjusts the frequency correction value that is being applied to the channelized output stream by the
|
* Sets the center frequency for this channel.
|
||||||
* polyphase channel output processor.
|
|
||||||
*
|
|
||||||
* @param value to apply for frequency correction in hertz
|
|
||||||
*/
|
|
||||||
protected void setChannelFrequencyCorrection(long value)
|
|
||||||
{
|
|
||||||
mChannelFrequencyCorrection = value;
|
|
||||||
updateFrequencyOffset();
|
|
||||||
broadcastConsumerSourceEvent(SourceEvent.frequencyCorrectionChange(mChannelFrequencyCorrection));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the center frequency for this channel.frequency for the incoming sample stream channel results and resets frequency correction to zero.
|
|
||||||
* @param frequency in hertz
|
* @param frequency in hertz
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setFrequency(long frequency)
|
public void setFrequency(long frequency)
|
||||||
{
|
{
|
||||||
mIndexCenterFrequency = frequency;
|
mIndexCenterFrequency = frequency;
|
||||||
|
}
|
||||||
|
|
||||||
//Set frequency correction to zero to trigger an update to the mixer and allow downstream monitors to
|
/**
|
||||||
//recalculate the frequency error correction again
|
* Center frequency from the incoming index channel(s).
|
||||||
setChannelFrequencyCorrection(0);
|
* @return frequency in hertz
|
||||||
|
*/
|
||||||
|
public long getIndexCenterFrequency()
|
||||||
|
{
|
||||||
|
return mIndexCenterFrequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the frequency offset required to mix the incoming signal to center the desired frequency
|
* Calculates the frequency offset required to mix the incoming signal to center the desired frequency
|
||||||
* within the channel
|
* within the channel
|
||||||
*/
|
*/
|
||||||
private long getFrequencyOffset()
|
public long getFrequencyOffset()
|
||||||
{
|
{
|
||||||
return mIndexCenterFrequency - getTunerChannel().getFrequency() + mChannelFrequencyCorrection;
|
return mIndexCenterFrequency - getTunerChannel().getFrequency();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the frequency offset being applied by the output processor. Calculates the offset using the center
|
|
||||||
* frequency of the incoming polyphase channel results and the desired output channel frequency adjusted by any
|
|
||||||
* requested frequency correction value.
|
|
||||||
*/
|
|
||||||
private void updateFrequencyOffset()
|
|
||||||
{
|
|
||||||
if(mPolyphaseChannelOutputProcessor != null)
|
|
||||||
{
|
|
||||||
mPolyphaseChannelOutputProcessor.setFrequencyOffset(getFrequencyOffset());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -324,4 +356,40 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
|||||||
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
|
return "POLYPHASE [" + mPolyphaseChannelOutputProcessor.getInputChannelCount() + "] " +
|
||||||
getTunerChannel().getFrequency();
|
getTunerChannel().getFrequency();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Support classes needed to update the output processor.
|
||||||
|
*/
|
||||||
|
public class PendingOutputProcessorUpdate
|
||||||
|
{
|
||||||
|
private final ChannelCalculator mChannelCalculator;
|
||||||
|
private final SynthesisFilterManager mSynthesisFilterManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param channelCalculator for support
|
||||||
|
* @param synthesisFilterManager for support
|
||||||
|
*/
|
||||||
|
public PendingOutputProcessorUpdate(ChannelCalculator channelCalculator, SynthesisFilterManager synthesisFilterManager)
|
||||||
|
{
|
||||||
|
mChannelCalculator = channelCalculator;
|
||||||
|
mSynthesisFilterManager = synthesisFilterManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel calculator
|
||||||
|
*/
|
||||||
|
public ChannelCalculator getChannelCalculator()
|
||||||
|
{
|
||||||
|
return mChannelCalculator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Channel calculator
|
||||||
|
*/
|
||||||
|
public SynthesisFilterManager getSynthesisFilterManager()
|
||||||
|
{
|
||||||
|
return mSynthesisFilterManager;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
package io.github.dsheirer.dsp.filter.channelizer;
|
package io.github.dsheirer.dsp.filter.channelizer;
|
||||||
|
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
|
import java.util.Arrays;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
import org.apache.commons.math3.util.FastMath;
|
||||||
import org.jtransforms.fft.FloatFFT_1D;
|
import org.jtransforms.fft.FloatFFT_1D;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -65,6 +66,20 @@ public class TwoChannelSynthesizerM2
|
|||||||
init(filter);
|
init(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of the state/configuration of this synthesizer to support debugging.
|
||||||
|
* @return state description.
|
||||||
|
*/
|
||||||
|
public String getStateDescription()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Two-Channel Synthesizer");
|
||||||
|
sb.append("\n\t\t Interleaved Filter: " + Arrays.toString(mIQInterleavedFilter));
|
||||||
|
sb.append("\n\t\t Serpentine Buffer Length: ").append(mSerpentineDataBuffer.length);
|
||||||
|
sb.append("\n\t\t Filter Vector Product Length: ").append(mFilterVectorProduct.length);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the synthesizer filter and data buffers for operation.
|
* Initializes the synthesizer filter and data buffers for operation.
|
||||||
*
|
*
|
||||||
@@ -73,7 +88,6 @@ public class TwoChannelSynthesizerM2
|
|||||||
private void init(float[] filter)
|
private void init(float[] filter)
|
||||||
{
|
{
|
||||||
int tapsPerChannel = (int) FastMath.ceil(filter.length / 2);
|
int tapsPerChannel = (int) FastMath.ceil(filter.length / 2);
|
||||||
|
|
||||||
mIQInterleavedFilter = getInterleavedFilter(filter, tapsPerChannel);
|
mIQInterleavedFilter = getInterleavedFilter(filter, tapsPerChannel);
|
||||||
mSerpentineDataBuffer = new float[mIQInterleavedFilter.length];
|
mSerpentineDataBuffer = new float[mIQInterleavedFilter.length];
|
||||||
mFilterVectorProduct = new float[mIQInterleavedFilter.length];
|
mFilterVectorProduct = new float[mIQInterleavedFilter.length];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,8 +20,8 @@ package io.github.dsheirer.dsp.filter.channelizer.output;
|
|||||||
|
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
|
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
|
||||||
import io.github.dsheirer.util.Dispatcher;
|
import io.github.dsheirer.util.Dispatcher;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -31,6 +31,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
|||||||
private final static Logger mLog = LoggerFactory.getLogger(ChannelOutputProcessor.class);
|
private final static Logger mLog = LoggerFactory.getLogger(ChannelOutputProcessor.class);
|
||||||
|
|
||||||
private Dispatcher<List<float[]>> mChannelResultsDispatcher;
|
private Dispatcher<List<float[]>> mChannelResultsDispatcher;
|
||||||
|
private HeartbeatManager mHeartbeatManager;
|
||||||
protected Listener<ComplexSamples> mComplexSamplesListener;
|
protected Listener<ComplexSamples> mComplexSamplesListener;
|
||||||
private int mInputChannelCount;
|
private int mInputChannelCount;
|
||||||
private long mCurrentSampleTimestamp = System.currentTimeMillis();
|
private long mCurrentSampleTimestamp = System.currentTimeMillis();
|
||||||
@@ -42,12 +43,25 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
|||||||
* @param inputChannelCount is the number of input channels for this output processor
|
* @param inputChannelCount is the number of input channels for this output processor
|
||||||
* @param sampleRate of the output channel. This is used to match the oscillator's sample rate to the output
|
* @param sampleRate of the output channel. This is used to match the oscillator's sample rate to the output
|
||||||
* channel sample rate for frequency translation/correction.
|
* channel sample rate for frequency translation/correction.
|
||||||
|
* @param heartbeatManager to receive pings on the dispatcher thread
|
||||||
*/
|
*/
|
||||||
public ChannelOutputProcessor(int inputChannelCount, double sampleRate)
|
public ChannelOutputProcessor(int inputChannelCount, double sampleRate, HeartbeatManager heartbeatManager)
|
||||||
{
|
{
|
||||||
mInputChannelCount = inputChannelCount;
|
mInputChannelCount = inputChannelCount;
|
||||||
mChannelResultsDispatcher = new Dispatcher<>((int)sampleRate, "sdrtrunk polyphase channel", Collections.emptyList());
|
//Process 1/10th of the sample rate per second at a rate of 20 times a second (200% of anticipated rate)
|
||||||
mChannelResultsDispatcher.setListener(floats -> process(floats));
|
mHeartbeatManager = heartbeatManager;
|
||||||
|
mChannelResultsDispatcher = new Dispatcher("sdrtrunk polyphase channel", (int)(sampleRate / 10),
|
||||||
|
50, mHeartbeatManager);
|
||||||
|
mChannelResultsDispatcher.setListener(floats -> {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
process(floats);
|
||||||
|
}
|
||||||
|
catch(Throwable t)
|
||||||
|
{
|
||||||
|
mLog.error("Error processing channel results", t);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,6 +24,11 @@ import java.util.List;
|
|||||||
|
|
||||||
public interface IPolyphaseChannelOutputProcessor
|
public interface IPolyphaseChannelOutputProcessor
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Description of the state/configuration of this output processor
|
||||||
|
*/
|
||||||
|
String getStateDescription();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start processing channel results
|
* Start processing channel results
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||||
|
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
|
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -36,15 +37,27 @@ public class OneChannelOutputProcessor extends ChannelOutputProcessor
|
|||||||
* @param sampleRate of the output sample stream.
|
* @param sampleRate of the output sample stream.
|
||||||
* @param channelIndexes containing a single channel index.
|
* @param channelIndexes containing a single channel index.
|
||||||
* @param gain value to apply. This is typically the same as the channelizer's channel count.
|
* @param gain value to apply. This is typically the same as the channelizer's channel count.
|
||||||
|
* @param heartbeatManager to receive heartbeats on the dispatch thread
|
||||||
*/
|
*/
|
||||||
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float gain)
|
public OneChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float gain,
|
||||||
|
HeartbeatManager heartbeatManager)
|
||||||
{
|
{
|
||||||
super(1, sampleRate);
|
super(1, sampleRate, heartbeatManager);
|
||||||
setPolyphaseChannelIndices(channelIndexes);
|
setPolyphaseChannelIndices(channelIndexes);
|
||||||
mMixerAssembler = new OneChannelMixerAssembler(gain);
|
mMixerAssembler = new OneChannelMixerAssembler(gain);
|
||||||
mMixerAssembler.getMixer().setSampleRate(sampleRate);
|
mMixerAssembler.getMixer().setSampleRate(sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStateDescription()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("One Channel Output Processor");
|
||||||
|
sb.append("\n\tIndex (doubled) [").append(mChannelOffset).append("]");
|
||||||
|
sb.append("\n\tMixer Assembler: none");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSynthesisFilter(float[] filter)
|
public void setSynthesisFilter(float[] filter)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -42,6 +42,19 @@ public class TwoChannelMixerAssembler extends MixerAssembler
|
|||||||
super(gain);
|
super(gain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of the state/configuration of this mixer assembler.
|
||||||
|
*/
|
||||||
|
public String getStateDescription()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Two Channel Mixer Assembler - State Description:");
|
||||||
|
sb.append("\n\tTwo-Channel Synthesizer: ").append(mTwoChannelSynthesizer.getStateDescription());
|
||||||
|
sb.append("\n\tMixer Frequency: ").append(getMixer().getFrequency());
|
||||||
|
sb.append("\n\tGain: ").append(getGain().getGain());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the filter coefficients to use for two-channel synthesis
|
* Sets the filter coefficients to use for two-channel synthesis
|
||||||
* @param filter
|
* @param filter
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
package io.github.dsheirer.dsp.filter.channelizer.output;
|
package io.github.dsheirer.dsp.filter.channelizer.output;
|
||||||
|
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
|
import io.github.dsheirer.source.heartbeat.HeartbeatManager;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -37,18 +38,30 @@ public class TwoChannelOutputProcessor extends ChannelOutputProcessor
|
|||||||
* @param sampleRate of the output sample stream.
|
* @param sampleRate of the output sample stream.
|
||||||
* @param channelIndexes containing two channel indices.
|
* @param channelIndexes containing two channel indices.
|
||||||
* @param gain to apply to output. Typically this is equal to the channelizer's channel count.
|
* @param gain to apply to output. Typically this is equal to the channelizer's channel count.
|
||||||
|
* @param heartbeatManager to be pinged on the dispatcher thread
|
||||||
*/
|
*/
|
||||||
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, float gain)
|
public TwoChannelOutputProcessor(double sampleRate, List<Integer> channelIndexes, float[] filter, float gain,
|
||||||
|
HeartbeatManager heartbeatManager)
|
||||||
{
|
{
|
||||||
//Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency
|
//Set the frequency correction oscillator to 2 x output sample rate since we'll be correcting the frequency
|
||||||
//after synthesizing both input channels
|
//after synthesizing both input channels
|
||||||
super(2, sampleRate);
|
super(2, sampleRate, heartbeatManager);
|
||||||
setPolyphaseChannelIndices(channelIndexes);
|
setPolyphaseChannelIndices(channelIndexes);
|
||||||
mMixerAssembler = new TwoChannelMixerAssembler(gain);
|
mMixerAssembler = new TwoChannelMixerAssembler(gain);
|
||||||
mMixerAssembler.getMixer().setSampleRate(sampleRate);
|
mMixerAssembler.getMixer().setSampleRate(sampleRate);
|
||||||
setSynthesisFilter(filter);
|
setSynthesisFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStateDescription()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Two Channel Output Processor");
|
||||||
|
sb.append("\n\tIndices (doubled) 1 [").append(mChannelOffset1).append("] 2 [").append(mChannelOffset2).append("]");
|
||||||
|
sb.append("\n\tMixer Assembler: ").append(mMixerAssembler.getStateDescription());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
|
* Sets the frequency offset to apply to the incoming samples to mix the desired signal to baseband.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -41,7 +41,7 @@ public abstract class ComplexGain
|
|||||||
* Gain value that will be applied to complex samples
|
* Gain value that will be applied to complex samples
|
||||||
* @return gain value
|
* @return gain value
|
||||||
*/
|
*/
|
||||||
protected float getGain()
|
public float getGain()
|
||||||
{
|
{
|
||||||
return mGain;
|
return mGain;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -72,6 +72,15 @@ public abstract class ComplexMixer
|
|||||||
return mOscillator.hasFrequency();
|
return mOscillator.hasFrequency();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current frequency of this oscillator
|
||||||
|
* @return frequency in hertz
|
||||||
|
*/
|
||||||
|
public double getFrequency()
|
||||||
|
{
|
||||||
|
return mOscillator.getFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the sample rate of the underlying oscillator
|
* Sets the sample rate of the underlying oscillator
|
||||||
* @param sampleRate in Hertz
|
* @param sampleRate in Hertz
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.dsp.oscillator;
|
||||||
|
|
||||||
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additive white gaussian noise wrapper around a complex oscillator that mixes in gaussian noise to the complex
|
||||||
|
* samples generated by the underlying complex oscillator.
|
||||||
|
*/
|
||||||
|
public class AWGNOscillator implements IComplexOscillator
|
||||||
|
{
|
||||||
|
private final Random mRandom = new Random();
|
||||||
|
private IComplexOscillator mComplexOscillator;
|
||||||
|
private float mNoiseScale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param complexOscillator to generate samples
|
||||||
|
* @param noiseScale to apply to generated samples to reduce the power of the noise and increase the SNR.
|
||||||
|
*/
|
||||||
|
public AWGNOscillator(IComplexOscillator complexOscillator, float noiseScale)
|
||||||
|
{
|
||||||
|
mComplexOscillator = complexOscillator;
|
||||||
|
mNoiseScale = noiseScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce the next scaled noise sample.
|
||||||
|
* @return next scaled noise sample.
|
||||||
|
*/
|
||||||
|
private float nextNoise()
|
||||||
|
{
|
||||||
|
return (float)mRandom.nextGaussian() * mNoiseScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] generate(int sampleCount)
|
||||||
|
{
|
||||||
|
float[] generated = mComplexOscillator.generate(sampleCount);
|
||||||
|
|
||||||
|
for(int x = 0; x < generated.length; x++)
|
||||||
|
{
|
||||||
|
generated[x] += nextNoise();
|
||||||
|
}
|
||||||
|
|
||||||
|
return generated;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComplexSamples generateComplexSamples(int sampleCount, long timestamp)
|
||||||
|
{
|
||||||
|
ComplexSamples samples = mComplexOscillator.generateComplexSamples(sampleCount, timestamp);
|
||||||
|
|
||||||
|
for(int x = 0; x < samples.i().length; x++)
|
||||||
|
{
|
||||||
|
samples.i()[x] += nextNoise();
|
||||||
|
samples.q()[x] += nextNoise();
|
||||||
|
}
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getFrequency()
|
||||||
|
{
|
||||||
|
return mComplexOscillator.getFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFrequency(double frequency)
|
||||||
|
{
|
||||||
|
mComplexOscillator.setFrequency(frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasFrequency()
|
||||||
|
{
|
||||||
|
return mComplexOscillator.hasFrequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getSampleRate()
|
||||||
|
{
|
||||||
|
return mComplexOscillator.getSampleRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSampleRate(double sampleRate)
|
||||||
|
{
|
||||||
|
mComplexOscillator.setSampleRate(sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled()
|
||||||
|
{
|
||||||
|
return mComplexOscillator.isEnabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.dsp.psk.pll;
|
package io.github.dsheirer.dsp.psk.pll;
|
||||||
|
|
||||||
@@ -101,7 +98,9 @@ public class FrequencyCorrectionSyncMonitor implements ISyncDetectListener, IFre
|
|||||||
@Override
|
@Override
|
||||||
public void processFrequencyError(long frequencyError)
|
public void processFrequencyError(long frequencyError)
|
||||||
{
|
{
|
||||||
//Only rebroadcast the frequency error if the sync count is more than 2
|
mFeedbackDecoder.broadcast(SourceEvent.pllFrequencyMeasurement(frequencyError));
|
||||||
|
|
||||||
|
//Only rebroadcast as a frequency error measurement if the sync count is more than 2
|
||||||
if(mSyncCount > 2)
|
if(mSyncCount > 2)
|
||||||
{
|
{
|
||||||
mFeedbackDecoder.broadcast(SourceEvent.frequencyErrorMeasurement(frequencyError));
|
mFeedbackDecoder.broadcast(SourceEvent.frequencyErrorMeasurement(frequencyError));
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.edac;
|
package io.github.dsheirer.edac;
|
||||||
@@ -34,7 +31,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
*/
|
*/
|
||||||
public class BerlekempMassey
|
public class BerlekempMassey
|
||||||
{
|
{
|
||||||
public final static Logger mLog = LoggerFactory.getLogger(BerlekempMassey.class);
|
private final static Logger mLog = LoggerFactory.getLogger(BerlekempMassey.class);
|
||||||
|
|
||||||
/* Golay field size GF( 2 ** MM ) */
|
/* Golay field size GF( 2 ** MM ) */
|
||||||
private int MM;
|
private int MM;
|
||||||
|
|||||||
66
src/main/java/io/github/dsheirer/edac/Checksum_5_DMR.java
Normal file
66
src/main/java/io/github/dsheirer/edac/Checksum_5_DMR.java
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General 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.edac;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the 5-bit Checksum error detection algorithm specified in TS 102 361-1 paragraph B.3.11
|
||||||
|
*/
|
||||||
|
public class Checksum_5_DMR
|
||||||
|
{
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(Checksum_5_DMR.class);
|
||||||
|
|
||||||
|
private static final int[] LC_0 = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
private static final int[] LC_1 = {8, 9, 10, 11, 12, 13, 14, 15};
|
||||||
|
private static final int[] LC_2 = {16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
private static final int[] LC_3 = {24, 25, 26, 27, 28, 29, 30, 31};
|
||||||
|
private static final int[] LC_4 = {32, 33, 34, 35, 36, 37, 38, 39};
|
||||||
|
private static final int[] LC_5 = {40, 41, 42, 43, 44, 45, 46, 47};
|
||||||
|
private static final int[] LC_6 = {48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] LC_7 = {56, 57, 58, 59, 60, 61, 62, 63};
|
||||||
|
private static final int[] LC_8 = {64, 65, 66, 67, 68, 69, 70, 71};
|
||||||
|
private static final int[] CHECKSUM = {72, 73, 74, 75, 76};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the 77-bit Full Link Control message is valid.
|
||||||
|
* @param message to check
|
||||||
|
* @return true if the checksum matches.
|
||||||
|
*/
|
||||||
|
public static boolean isValid(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
int accumulator = message.getInt(LC_0);
|
||||||
|
accumulator += message.getInt(LC_1);
|
||||||
|
accumulator += message.getInt(LC_2);
|
||||||
|
accumulator += message.getInt(LC_3);
|
||||||
|
accumulator += message.getInt(LC_4);
|
||||||
|
accumulator += message.getInt(LC_5);
|
||||||
|
accumulator += message.getInt(LC_6);
|
||||||
|
accumulator += message.getInt(LC_7);
|
||||||
|
accumulator += message.getInt(LC_8);
|
||||||
|
|
||||||
|
accumulator = accumulator % 31;
|
||||||
|
int checksum = message.getInt(CHECKSUM);
|
||||||
|
|
||||||
|
return accumulator == checksum;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,12 +20,16 @@
|
|||||||
package io.github.dsheirer.edac;
|
package io.github.dsheirer.edac;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Berlecamp-Massey decoder for Reed Solomon RS(12,9,4) protected messages using DMR generator polynomial
|
* Berlecamp-Massey decoder for Reed Solomon RS(12,9,4) protected messages using DMR generator polynomial
|
||||||
*/
|
*/
|
||||||
public class ReedSolomon_12_9_4_DMR extends ReedSolomon_255_DMR
|
public class ReedSolomon_12_9_4_DMR extends ReedSolomon_255_DMR
|
||||||
{
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(ReedSolomon_12_9_4_DMR.class);
|
||||||
|
|
||||||
private static final int[] FLC_CODEWORD_0 = {0, 1, 2, 3, 4, 5, 6, 7};
|
private static final int[] FLC_CODEWORD_0 = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
private static final int[] FLC_CODEWORD_1 = {8, 9, 10, 11, 12, 13, 14, 15};
|
private static final int[] FLC_CODEWORD_1 = {8, 9, 10, 11, 12, 13, 14, 15};
|
||||||
private static final int[] FLC_CODEWORD_2 = {16, 17, 18, 19, 20, 21, 22, 23};
|
private static final int[] FLC_CODEWORD_2 = {16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
@@ -56,16 +60,16 @@ public class ReedSolomon_12_9_4_DMR extends ReedSolomon_255_DMR
|
|||||||
*
|
*
|
||||||
* @param message to be corrected.
|
* @param message to be corrected.
|
||||||
* @param crcMask as defined in the TS 102-361-1 Table B.21
|
* @param crcMask as defined in the TS 102-361-1 Table B.21
|
||||||
* @return true if the message was corrected.
|
* @return true if the message is correct or if the message was corrected.
|
||||||
*/
|
*/
|
||||||
public boolean correctFullLinkControl(CorrectedBinaryMessage message, int crcMask)
|
public boolean correctFullLinkControl(CorrectedBinaryMessage message, int crcMask)
|
||||||
{
|
{
|
||||||
int[] input = new int[255];
|
int[] input = new int[255];
|
||||||
int[] output = new int[255];
|
int[] output = new int[255];
|
||||||
|
|
||||||
input[0] = message.getInt(FLC_PARITY_2) ^ crcMask;
|
input[0] = (message.getInt(FLC_PARITY_2) ^ crcMask);
|
||||||
input[1] = message.getInt(FLC_PARITY_1) ^ crcMask;
|
input[1] = (message.getInt(FLC_PARITY_1) ^ crcMask);
|
||||||
input[2] = message.getInt(FLC_PARITY_0) ^ crcMask;
|
input[2] = (message.getInt(FLC_PARITY_0) ^ crcMask);
|
||||||
input[3] = message.getInt(FLC_CODEWORD_8);
|
input[3] = message.getInt(FLC_CODEWORD_8);
|
||||||
input[4] = message.getInt(FLC_CODEWORD_7);
|
input[4] = message.getInt(FLC_CODEWORD_7);
|
||||||
input[5] = message.getInt(FLC_CODEWORD_6);
|
input[5] = message.getInt(FLC_CODEWORD_6);
|
||||||
@@ -106,4 +110,40 @@ public class ReedSolomon_12_9_4_DMR extends ReedSolomon_255_DMR
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
int[] output = new int[255];
|
||||||
|
int[] input = new int[255];
|
||||||
|
input[0] = 247;
|
||||||
|
input[1] = 236;
|
||||||
|
input[2] = 171;
|
||||||
|
input[3] = 24;
|
||||||
|
input[4] = 46;
|
||||||
|
input[5] = 177;
|
||||||
|
input[6] = 246;
|
||||||
|
input[7] = 205;
|
||||||
|
input[8] = 232;
|
||||||
|
input[9] = 32;
|
||||||
|
input[10] = 124;
|
||||||
|
input[11] = 0;
|
||||||
|
|
||||||
|
// input[254] = 247;
|
||||||
|
// input[253] = 236;
|
||||||
|
// input[252] = 171;
|
||||||
|
// input[251] = 24;
|
||||||
|
// input[250] = 46;
|
||||||
|
// input[249] = 177;
|
||||||
|
// input[248] = 246;
|
||||||
|
// input[247] = 205;
|
||||||
|
// input[246] = 232;
|
||||||
|
// input[245] = 32;
|
||||||
|
// input[244] = 124;
|
||||||
|
// input[243] = 0;
|
||||||
|
|
||||||
|
ReedSolomon_12_9_4_DMR rs = new ReedSolomon_12_9_4_DMR();
|
||||||
|
boolean irrecoverable = rs.decode(input, output);
|
||||||
|
|
||||||
|
mLog.info("Irrecoverable: " + irrecoverable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,6 +24,8 @@ import io.github.dsheirer.alias.AliasModel;
|
|||||||
import io.github.dsheirer.controller.channel.map.ChannelMap;
|
import io.github.dsheirer.controller.channel.map.ChannelMap;
|
||||||
import io.github.dsheirer.controller.channel.map.ChannelRange;
|
import io.github.dsheirer.controller.channel.map.ChannelRange;
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
import io.github.dsheirer.eventbus.MyEventBus;
|
||||||
|
import io.github.dsheirer.gui.dmr.DMRRecordingViewer;
|
||||||
|
import io.github.dsheirer.gui.dmr.ViewDmrRecordingViewerRequest;
|
||||||
import io.github.dsheirer.gui.icon.IconManager;
|
import io.github.dsheirer.gui.icon.IconManager;
|
||||||
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
|
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
|
||||||
import io.github.dsheirer.gui.playlist.PlaylistEditor;
|
import io.github.dsheirer.gui.playlist.PlaylistEditor;
|
||||||
@@ -65,6 +67,7 @@ public class JavaFxWindowManager extends Application
|
|||||||
public static final String USER_PREFERENCES_EDITOR = "preferences";
|
public static final String USER_PREFERENCES_EDITOR = "preferences";
|
||||||
public static final String STAGE_MONITOR_KEY_CALIBRATION_DIALOG = "calibration.dialog";
|
public static final String STAGE_MONITOR_KEY_CALIBRATION_DIALOG = "calibration.dialog";
|
||||||
public static final String STAGE_MONITOR_KEY_CHANNEL_MAP_EDITOR = "channel.map";
|
public static final String STAGE_MONITOR_KEY_CHANNEL_MAP_EDITOR = "channel.map";
|
||||||
|
public static final String STAGE_MONITOR_KEY_DMR_MESSAGE_VIEWER = "dmr.message.viewer";
|
||||||
public static final String STAGE_MONITOR_KEY_ICON_MANAGER_EDITOR = "icon.manager";
|
public static final String STAGE_MONITOR_KEY_ICON_MANAGER_EDITOR = "icon.manager";
|
||||||
public static final String STAGE_MONITOR_KEY_JMBE_EDITOR = "jmbe.editor";
|
public static final String STAGE_MONITOR_KEY_JMBE_EDITOR = "jmbe.editor";
|
||||||
public static final String STAGE_MONITOR_KEY_PLAYLIST_EDITOR = "playlist";
|
public static final String STAGE_MONITOR_KEY_PLAYLIST_EDITOR = "playlist";
|
||||||
@@ -79,12 +82,14 @@ public class JavaFxWindowManager extends Application
|
|||||||
private TunerManager mTunerManager;
|
private TunerManager mTunerManager;
|
||||||
private UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
private UserPreferencesEditor mUserPreferencesEditor;
|
private UserPreferencesEditor mUserPreferencesEditor;
|
||||||
|
private DMRRecordingViewer mDmrRecordingViewer;
|
||||||
|
|
||||||
private Stage mChannelMapStage;
|
private Stage mChannelMapStage;
|
||||||
private Stage mIconManagerStage;
|
private Stage mIconManagerStage;
|
||||||
private Stage mJmbeEditorStage;
|
private Stage mJmbeEditorStage;
|
||||||
private Stage mPlaylistStage;
|
private Stage mPlaylistStage;
|
||||||
private Stage mUserPreferencesStage;
|
private Stage mUserPreferencesStage;
|
||||||
|
private Stage mDmrRecordingViewerStage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance. Note: this constructor is used for Swing applications.
|
* Constructs an instance. Note: this constructor is used for Swing applications.
|
||||||
@@ -189,6 +194,34 @@ public class JavaFxWindowManager extends Application
|
|||||||
return new CalibrationDialog(userPreferences);
|
return new CalibrationDialog(userPreferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stage for the DMR Message Viewer
|
||||||
|
*/
|
||||||
|
public Stage getDmrRecordingViewerStage()
|
||||||
|
{
|
||||||
|
if(mDmrRecordingViewerStage == null)
|
||||||
|
{
|
||||||
|
createJFXPanel();
|
||||||
|
Scene scene = new Scene(getDmrRecordingViewer(), 1100, 800);
|
||||||
|
mDmrRecordingViewerStage = new Stage();
|
||||||
|
mDmrRecordingViewerStage.setTitle("sdrtrunk - DMR Recording Viewer");
|
||||||
|
mDmrRecordingViewerStage.setScene(scene);
|
||||||
|
mUserPreferences.getJavaFxPreferences().monitor(mDmrRecordingViewerStage, STAGE_MONITOR_KEY_DMR_MESSAGE_VIEWER);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mDmrRecordingViewerStage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DMRRecordingViewer getDmrRecordingViewer()
|
||||||
|
{
|
||||||
|
if(mDmrRecordingViewer == null)
|
||||||
|
{
|
||||||
|
mDmrRecordingViewer = new DMRRecordingViewer();
|
||||||
|
}
|
||||||
|
|
||||||
|
return mDmrRecordingViewer;
|
||||||
|
}
|
||||||
|
|
||||||
public Stage getIconManagerStage()
|
public Stage getIconManagerStage()
|
||||||
{
|
{
|
||||||
if(mIconManagerStage == null)
|
if(mIconManagerStage == null)
|
||||||
@@ -416,6 +449,19 @@ public class JavaFxWindowManager extends Application
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a channel map editor request
|
||||||
|
*/
|
||||||
|
@Subscribe
|
||||||
|
public void process(final ViewDmrRecordingViewerRequest request)
|
||||||
|
{
|
||||||
|
execute(() -> {
|
||||||
|
getDmrRecordingViewerStage().show();
|
||||||
|
getDmrRecordingViewerStage().requestFocus();
|
||||||
|
getDmrRecordingViewerStage().toFront();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage primaryStage) throws Exception
|
public void start(Stage primaryStage) throws Exception
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -32,6 +32,7 @@ import io.github.dsheirer.controller.channel.ChannelAutoStartFrame;
|
|||||||
import io.github.dsheirer.controller.channel.ChannelException;
|
import io.github.dsheirer.controller.channel.ChannelException;
|
||||||
import io.github.dsheirer.controller.channel.ChannelSelectionManager;
|
import io.github.dsheirer.controller.channel.ChannelSelectionManager;
|
||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
import io.github.dsheirer.eventbus.MyEventBus;
|
||||||
|
import io.github.dsheirer.gui.dmr.ViewDmrRecordingViewerRequest;
|
||||||
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
|
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
|
||||||
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
|
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
|
||||||
import io.github.dsheirer.gui.preference.CalibrateRequest;
|
import io.github.dsheirer.gui.preference.CalibrateRequest;
|
||||||
@@ -59,6 +60,26 @@ import io.github.dsheirer.spectrum.SpectralDisplayPanel;
|
|||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import io.github.dsheirer.util.TimeStamp;
|
import io.github.dsheirer.util.TimeStamp;
|
||||||
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
||||||
|
import java.awt.AWTException;
|
||||||
|
import java.awt.Desktop;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.GraphicsEnvironment;
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.Robot;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.FileSystems;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.ButtonType;
|
||||||
import jiconfont.icons.font_awesome.FontAwesome;
|
import jiconfont.icons.font_awesome.FontAwesome;
|
||||||
@@ -80,26 +101,6 @@ import javax.swing.UIManager;
|
|||||||
import javax.swing.event.MenuEvent;
|
import javax.swing.event.MenuEvent;
|
||||||
import javax.swing.event.MenuListener;
|
import javax.swing.event.MenuListener;
|
||||||
import javax.swing.plaf.metal.MetalLookAndFeel;
|
import javax.swing.plaf.metal.MetalLookAndFeel;
|
||||||
import java.awt.AWTException;
|
|
||||||
import java.awt.Desktop;
|
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.EventQueue;
|
|
||||||
import java.awt.GraphicsEnvironment;
|
|
||||||
import java.awt.Point;
|
|
||||||
import java.awt.Robot;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.awt.event.KeyEvent;
|
|
||||||
import java.awt.event.WindowAdapter;
|
|
||||||
import java.awt.event.WindowEvent;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.FileSystems;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class SDRTrunk implements Listener<TunerEvent>
|
public class SDRTrunk implements Listener<TunerEvent>
|
||||||
{
|
{
|
||||||
@@ -247,7 +248,11 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
{
|
{
|
||||||
mMainGui.setVisible(true);
|
mMainGui.setVisible(true);
|
||||||
Tuner tuner = tunerSpectralDisplayManager.showFirstTuner();
|
Tuner tuner = tunerSpectralDisplayManager.showFirstTuner();
|
||||||
updateTitle(tuner);
|
|
||||||
|
if(tuner != null)
|
||||||
|
{
|
||||||
|
updateTitle(tuner.getPreferredName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(calibrating && !GraphicsEnvironment.isHeadless())
|
if(calibrating && !GraphicsEnvironment.isHeadless())
|
||||||
@@ -308,7 +313,7 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
new ChannelAutoStartFrame(mPlaylistManager.getChannelProcessingManager(), channels);
|
new ChannelAutoStartFrame(mPlaylistManager.getChannelProcessingManager(), channels, mUserPreferences);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,9 +418,11 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
viewPlaylistItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewPlaylistRequest()));
|
viewPlaylistItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewPlaylistRequest()));
|
||||||
viewMenu.add(viewPlaylistItem);
|
viewMenu.add(viewPlaylistItem);
|
||||||
|
|
||||||
JMenuItem preferencesItem = new JMenuItem("User Preferences");
|
viewMenu.add(new JSeparator());
|
||||||
preferencesItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest()));
|
|
||||||
viewMenu.add(preferencesItem);
|
JMenuItem dmrMessageViewerMenu = new JMenuItem("DMR Recording Viewer");
|
||||||
|
dmrMessageViewerMenu.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewDmrRecordingViewerRequest()));
|
||||||
|
viewMenu.add(dmrMessageViewerMenu);
|
||||||
|
|
||||||
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
|
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
|
||||||
settingsMenu.addActionListener(arg0 -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
|
settingsMenu.addActionListener(arg0 -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
|
||||||
@@ -436,15 +443,19 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
mLog.error("Couldn't open file explorer");
|
mLog.error("Couldn't open file explorer");
|
||||||
|
|
||||||
JOptionPane.showMessageDialog(mMainGui,
|
JOptionPane.showMessageDialog(mMainGui,
|
||||||
"Can't launch file explorer - files are located at: " +
|
"Can't launch file explorer - files are located at: " +
|
||||||
getHomePath().toString(),
|
getHomePath().toString(),
|
||||||
"Can't launch file explorer",
|
"Can't launch file explorer",
|
||||||
JOptionPane.ERROR_MESSAGE);
|
JOptionPane.ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
viewMenu.add(logFilesMenu);
|
viewMenu.add(logFilesMenu);
|
||||||
|
|
||||||
|
JMenuItem preferencesItem = new JMenuItem("User Preferences");
|
||||||
|
preferencesItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest()));
|
||||||
|
viewMenu.add(preferencesItem);
|
||||||
|
|
||||||
viewMenu.add(new JSeparator());
|
viewMenu.add(new JSeparator());
|
||||||
viewMenu.add(new TunersMenu());
|
viewMenu.add(new TunersMenu());
|
||||||
viewMenu.add(new JSeparator());
|
viewMenu.add(new JSeparator());
|
||||||
@@ -623,21 +634,34 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
@Override
|
@Override
|
||||||
public void receive(TunerEvent event)
|
public void receive(TunerEvent event)
|
||||||
{
|
{
|
||||||
if(event.getEvent() == TunerEvent.Event.REQUEST_MAIN_SPECTRAL_DISPLAY)
|
switch(event.getEvent())
|
||||||
{
|
{
|
||||||
updateTitle(event.getTuner());
|
case REQUEST_MAIN_SPECTRAL_DISPLAY:
|
||||||
|
updateTitle(event.getTuner().getPreferredName());
|
||||||
|
break;
|
||||||
|
case REQUEST_CLEAR_MAIN_SPECTRAL_DISPLAY:
|
||||||
|
updateTitle(null);
|
||||||
|
break;
|
||||||
|
case NOTIFICATION_SHUTTING_DOWN:
|
||||||
|
Tuner currentTuner = mSpectralPanel.getTuner();
|
||||||
|
|
||||||
|
if(event.hasTuner() && event.getTuner().equals(currentTuner) || currentTuner == null)
|
||||||
|
{
|
||||||
|
updateTitle(null);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the title bar with the tuner name
|
* Updates the title bar with the tuner name
|
||||||
* @param tuner optional
|
* @param tunerName optional
|
||||||
*/
|
*/
|
||||||
private void updateTitle(Tuner tuner)
|
private void updateTitle(String tunerName)
|
||||||
{
|
{
|
||||||
if(tuner != null)
|
if(tunerName != null)
|
||||||
{
|
{
|
||||||
mMainGui.setTitle(mTitle + " - " + tuner.getPreferredName());
|
mMainGui.setTitle(mTitle + " - " + tunerName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,6 +20,7 @@ package io.github.dsheirer.gui.channelizer;
|
|||||||
|
|
||||||
import io.github.dsheirer.buffer.FloatNativeBuffer;
|
import io.github.dsheirer.buffer.FloatNativeBuffer;
|
||||||
import io.github.dsheirer.buffer.INativeBuffer;
|
import io.github.dsheirer.buffer.INativeBuffer;
|
||||||
|
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelSource;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
import io.github.dsheirer.settings.SettingsManager;
|
import io.github.dsheirer.settings.SettingsManager;
|
||||||
@@ -36,6 +37,12 @@ import io.github.dsheirer.spectrum.ComplexDftProcessor;
|
|||||||
import io.github.dsheirer.spectrum.DFTSize;
|
import io.github.dsheirer.spectrum.DFTSize;
|
||||||
import io.github.dsheirer.spectrum.SpectrumPanel;
|
import io.github.dsheirer.spectrum.SpectrumPanel;
|
||||||
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
|
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -50,12 +57,6 @@ import javax.swing.JToggleButton;
|
|||||||
import javax.swing.SpinnerNumberModel;
|
import javax.swing.SpinnerNumberModel;
|
||||||
import javax.swing.event.ChangeEvent;
|
import javax.swing.event.ChangeEvent;
|
||||||
import javax.swing.event.ChangeListener;
|
import javax.swing.event.ChangeListener;
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.EventQueue;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ChannelizerViewer2 extends JFrame
|
public class ChannelizerViewer2 extends JFrame
|
||||||
{
|
{
|
||||||
@@ -194,9 +195,9 @@ public class ChannelizerViewer2 extends JFrame
|
|||||||
|
|
||||||
getTopChannelArrayPanel().addChannel(tunerChannel);
|
getTopChannelArrayPanel().addChannel(tunerChannel);
|
||||||
}
|
}
|
||||||
catch(Exception exception)
|
catch(Exception e1)
|
||||||
{
|
{
|
||||||
mLog.error("Can't parse frequency from value: " + value);
|
mLog.error("Can't parse frequency from value: " + value, e1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,9 +230,9 @@ public class ChannelizerViewer2 extends JFrame
|
|||||||
|
|
||||||
getBottomChannelArrayPanel().addChannel(tunerChannel);
|
getBottomChannelArrayPanel().addChannel(tunerChannel);
|
||||||
}
|
}
|
||||||
catch(Exception exception)
|
catch(Exception e1)
|
||||||
{
|
{
|
||||||
mLog.error("Can't parse frequency from value: " + value);
|
mLog.error("Can't parse frequency from value: " + value, e1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,6 +389,26 @@ public class ChannelizerViewer2 extends JFrame
|
|||||||
mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved()));
|
mComplexDftProcessor.receive(new FloatNativeBuffer(complexSamples.toInterleaved()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(mSource instanceof PolyphaseChannelSource pcs)
|
||||||
|
{ List<Integer> indexes = pcs.getOutputProcessorIndexes();
|
||||||
|
double sRate = pcs.getSampleRate();
|
||||||
|
long indexCenterFrequency = pcs.getIndexCenterFrequency();
|
||||||
|
long appliedFrequencyOffset = pcs.getFrequencyOffset();
|
||||||
|
long requestedCenterFrequency = pcs.getFrequency();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Polyphase Channel - Bandwidth: ").append(sRate);
|
||||||
|
sb.append(" Channel/Requested/Offset: ").append(indexCenterFrequency);
|
||||||
|
sb.append("/").append(requestedCenterFrequency);
|
||||||
|
sb.append("/").append(appliedFrequencyOffset);
|
||||||
|
sb.append(" Indexes: ").append(indexes);
|
||||||
|
sb.append(" Tuner SR:").append(pcs.getTunerSampleRate());
|
||||||
|
sb.append(" Tuner CF:").append(pcs.getTunerCenterFrequency());
|
||||||
|
mLog.info(sb.toString());
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
mSource.start();
|
mSource.start();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
538
src/main/java/io/github/dsheirer/gui/dmr/DMRRecordingViewer.java
Normal file
538
src/main/java/io/github/dsheirer/gui/dmr/DMRRecordingViewer.java
Normal file
@@ -0,0 +1,538 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General 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.gui.dmr;
|
||||||
|
|
||||||
|
import io.github.dsheirer.message.IMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRMessageFramer;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRMessageProcessor;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
||||||
|
import io.github.dsheirer.record.binary.BinaryReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.collections.transformation.FilteredList;
|
||||||
|
import javafx.collections.transformation.SortedList;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.control.TablePosition;
|
||||||
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.scene.input.Clipboard;
|
||||||
|
import javafx.scene.input.ClipboardContent;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyCodeCombination;
|
||||||
|
import javafx.scene.input.KeyCombination;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility application to load and view a DMR .bits recording file with the messages fully parsed.
|
||||||
|
*/
|
||||||
|
public class DMRRecordingViewer extends VBox
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(DMRRecordingViewer.class);
|
||||||
|
private static final KeyCodeCombination KEY_CODE_COPY = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY);
|
||||||
|
private static final String LAST_SELECTED_DIRECTORY = "last.selected.directory";
|
||||||
|
private Preferences mPreferences = Preferences.userNodeForPackage(DMRRecordingViewer.class);
|
||||||
|
private Scene mScene;
|
||||||
|
private Button mSelectFileButton;
|
||||||
|
private Label mSelectedFileLabel;
|
||||||
|
private TableView<IMessage> mMessageTableView;
|
||||||
|
private ObservableList<IMessage> mMessages = FXCollections.observableArrayList();
|
||||||
|
private FilteredList<IMessage> mFilteredMessages = new FilteredList<>(mMessages);
|
||||||
|
private CheckBox mShowTS0;
|
||||||
|
private CheckBox mShowTS1;
|
||||||
|
private CheckBox mShowTS2;
|
||||||
|
private CheckBox mUseCompressedTalkgroups;
|
||||||
|
private TextField mSearchText;
|
||||||
|
private TextField mFindText;
|
||||||
|
private Button mFindButton;
|
||||||
|
private Button mFindNextButton;
|
||||||
|
|
||||||
|
public DMRRecordingViewer()
|
||||||
|
{
|
||||||
|
setPadding(new Insets(5));
|
||||||
|
setSpacing(5);
|
||||||
|
|
||||||
|
HBox fileBox = new HBox();
|
||||||
|
fileBox.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
fileBox.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
fileBox.setSpacing(5);
|
||||||
|
HBox.setHgrow(getSelectFileButton(), Priority.NEVER);
|
||||||
|
HBox.setHgrow(getSelectedFileLabel(), Priority.ALWAYS);
|
||||||
|
getSelectedFileLabel().setAlignment(Pos.BASELINE_CENTER);
|
||||||
|
|
||||||
|
HBox compressedBox = new HBox();
|
||||||
|
compressedBox.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
compressedBox.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
compressedBox.getChildren().addAll(getUseCompressedTalkgroups());
|
||||||
|
HBox.setHgrow(compressedBox, Priority.ALWAYS);
|
||||||
|
|
||||||
|
fileBox.getChildren().addAll(getSelectFileButton(), getSelectedFileLabel(), compressedBox);
|
||||||
|
|
||||||
|
HBox filterBox = new HBox();
|
||||||
|
filterBox.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
filterBox.setAlignment(Pos.BASELINE_CENTER);
|
||||||
|
filterBox.setSpacing(5);
|
||||||
|
|
||||||
|
Label showLabel = new Label("Show:");
|
||||||
|
HBox.setMargin(showLabel, new Insets(0,0,0,15));
|
||||||
|
Label searchLabel = new Label("Message Filter:");
|
||||||
|
HBox.setMargin(searchLabel, new Insets(0,0,0,15));
|
||||||
|
Label findLabel = new Label("Find:");
|
||||||
|
|
||||||
|
HBox.setHgrow(getFindText(), Priority.ALWAYS);
|
||||||
|
HBox.setHgrow(getSearchText(), Priority.ALWAYS);
|
||||||
|
|
||||||
|
filterBox.getChildren().addAll(findLabel, getFindText(), getFindButton(), getFindNextButton(), searchLabel,
|
||||||
|
getSearchText(), showLabel, getShowTS0(), getShowTS1(), getShowTS2());
|
||||||
|
|
||||||
|
VBox.setVgrow(fileBox, Priority.NEVER);
|
||||||
|
VBox.setVgrow(filterBox, Priority.NEVER);
|
||||||
|
VBox.setVgrow(getMessageTableView(), Priority.ALWAYS);
|
||||||
|
|
||||||
|
getChildren().addAll(fileBox, filterBox, getMessageTableView());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the recording file and loads the content into the viewer
|
||||||
|
* @param file containing a .bits recording of decoded DMR data.
|
||||||
|
*/
|
||||||
|
private void load(File file)
|
||||||
|
{
|
||||||
|
if(file != null && file.exists())
|
||||||
|
{
|
||||||
|
getSelectedFileLabel().setText(file.getName());
|
||||||
|
mMessages.clear();
|
||||||
|
|
||||||
|
DMRMessageFramer messageFramer = new DMRMessageFramer(null);
|
||||||
|
DecodeConfigDMR config = new DecodeConfigDMR();
|
||||||
|
if(getUseCompressedTalkgroups().isSelected())
|
||||||
|
{
|
||||||
|
config.setUseCompressedTalkgroups(true);
|
||||||
|
}
|
||||||
|
DMRMessageProcessor messageProcessor = new DMRMessageProcessor(config);
|
||||||
|
messageFramer.setListener(messageProcessor);
|
||||||
|
messageProcessor.setMessageListener(message -> mMessages.add(message));
|
||||||
|
|
||||||
|
try(BinaryReader reader = new BinaryReader(file.toPath(), 200))
|
||||||
|
{
|
||||||
|
while(reader.hasNext())
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = reader.next();
|
||||||
|
messageFramer.receive(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception ioe)
|
||||||
|
{
|
||||||
|
ioe.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the filter(s) applies to the list of messages
|
||||||
|
*/
|
||||||
|
private void updateFilters()
|
||||||
|
{
|
||||||
|
Predicate<IMessage> timeslotPredicate = message ->
|
||||||
|
(getShowTS0().isSelected() && (message.getTimeslot() == 0)) ||
|
||||||
|
(getShowTS1().isSelected() && (message.getTimeslot() == 1)) ||
|
||||||
|
(getShowTS2().isSelected() && (message.getTimeslot() == 2));
|
||||||
|
|
||||||
|
String filterText = getSearchText().getText();
|
||||||
|
|
||||||
|
if(filterText == null || filterText.isEmpty())
|
||||||
|
{
|
||||||
|
mFilteredMessages.setPredicate(timeslotPredicate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Predicate<IMessage> textPredicate = message -> message.toString().toLowerCase().contains(filterText.toLowerCase());
|
||||||
|
mFilteredMessages.setPredicate(timeslotPredicate.and(textPredicate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds and selects the first row containing the text argument.
|
||||||
|
* @param text to search for.
|
||||||
|
*/
|
||||||
|
private void find(String text)
|
||||||
|
{
|
||||||
|
if(text != null && !text.isEmpty())
|
||||||
|
{
|
||||||
|
for(IMessage message: mFilteredMessages)
|
||||||
|
{
|
||||||
|
if(message.toString().toLowerCase().contains(text.toLowerCase()))
|
||||||
|
{
|
||||||
|
getMessageTableView().getSelectionModel().select(message);
|
||||||
|
getMessageTableView().scrollTo(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds and selects the first row containing the text argument, after the currently selected row.
|
||||||
|
* @param text to search for.
|
||||||
|
*/
|
||||||
|
private void findNext(String text)
|
||||||
|
{
|
||||||
|
if(text != null && !text.isEmpty())
|
||||||
|
{
|
||||||
|
IMessage selected = getMessageTableView().getSelectionModel().getSelectedItem();
|
||||||
|
|
||||||
|
if(selected == null)
|
||||||
|
{
|
||||||
|
find(text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int row = mFilteredMessages.indexOf(selected);
|
||||||
|
|
||||||
|
for(int x = row + 1; x < mFilteredMessages.size(); x++)
|
||||||
|
{
|
||||||
|
if(x < mFilteredMessages.size())
|
||||||
|
{
|
||||||
|
IMessage message = mFilteredMessages.get(x);
|
||||||
|
|
||||||
|
if(message.toString().toLowerCase().contains(text.toLowerCase()))
|
||||||
|
{
|
||||||
|
getMessageTableView().getSelectionModel().select(message);
|
||||||
|
getMessageTableView().scrollTo(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List view control with DMR messages
|
||||||
|
*/
|
||||||
|
private TableView<IMessage> getMessageTableView()
|
||||||
|
{
|
||||||
|
if(mMessageTableView == null)
|
||||||
|
{
|
||||||
|
mMessageTableView = new TableView<>();
|
||||||
|
SortedList<IMessage> sortedList = new SortedList<>(mFilteredMessages);
|
||||||
|
sortedList.comparatorProperty().bind(mMessageTableView.comparatorProperty());
|
||||||
|
mMessageTableView.setItems(sortedList);
|
||||||
|
|
||||||
|
mMessageTableView.setOnKeyPressed(event ->
|
||||||
|
{
|
||||||
|
if(KEY_CODE_COPY.match(event))
|
||||||
|
{
|
||||||
|
final Set<Integer> rows = new TreeSet<>();
|
||||||
|
for (final TablePosition tablePosition : mMessageTableView.getSelectionModel().getSelectedCells())
|
||||||
|
{
|
||||||
|
rows.add(tablePosition.getRow());
|
||||||
|
}
|
||||||
|
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
boolean firstRow = true;
|
||||||
|
for (final Integer row : rows)
|
||||||
|
{
|
||||||
|
if(firstRow)
|
||||||
|
{
|
||||||
|
firstRow = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean firstCol = true;
|
||||||
|
|
||||||
|
for (final TableColumn<?, ?> column : mMessageTableView.getColumns())
|
||||||
|
{
|
||||||
|
if(firstCol)
|
||||||
|
{
|
||||||
|
firstCol = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append('\t');
|
||||||
|
}
|
||||||
|
|
||||||
|
final Object cellData = column.getCellData(row);
|
||||||
|
sb.append(cellData == null ? "" : cellData.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final ClipboardContent clipboardContent = new ClipboardContent();
|
||||||
|
clipboardContent.putString(sb.toString());
|
||||||
|
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
TableColumn timestampColumn = new TableColumn();
|
||||||
|
timestampColumn.setPrefWidth(110);
|
||||||
|
timestampColumn.setText("Time");
|
||||||
|
timestampColumn.setCellValueFactory(new PropertyValueFactory<>("timestamp"));
|
||||||
|
|
||||||
|
TableColumn timeslotColumn = new TableColumn();
|
||||||
|
timeslotColumn.setPrefWidth(35);
|
||||||
|
timeslotColumn.setText("TS");
|
||||||
|
timeslotColumn.setCellValueFactory(new PropertyValueFactory<>("timeslot"));
|
||||||
|
|
||||||
|
TableColumn validColumn = new TableColumn();
|
||||||
|
validColumn.setPrefWidth(50);
|
||||||
|
validColumn.setText("Valid");
|
||||||
|
validColumn.setCellValueFactory(new PropertyValueFactory<>("valid"));
|
||||||
|
|
||||||
|
TableColumn messageColumn = new TableColumn();
|
||||||
|
messageColumn.setPrefWidth(1000);
|
||||||
|
messageColumn.setText("Message");
|
||||||
|
messageColumn.setCellValueFactory((Callback<TableColumn.CellDataFeatures, ObservableValue>) param -> {
|
||||||
|
SimpleStringProperty property = new SimpleStringProperty();
|
||||||
|
if(param.getValue() instanceof IMessage message)
|
||||||
|
{
|
||||||
|
property.set(message.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return property;
|
||||||
|
});
|
||||||
|
|
||||||
|
mMessageTableView.getColumns().addAll(timestampColumn, validColumn, timeslotColumn, messageColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMessageTableView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File selection button
|
||||||
|
* @return button
|
||||||
|
*/
|
||||||
|
private Button getSelectFileButton()
|
||||||
|
{
|
||||||
|
if(mSelectFileButton == null)
|
||||||
|
{
|
||||||
|
mSelectFileButton = new Button("Select ...");
|
||||||
|
mSelectFileButton.onActionProperty().set(event -> {
|
||||||
|
FileChooser fileChooser = new FileChooser();
|
||||||
|
fileChooser.setTitle("Select DMR .bits Recording");
|
||||||
|
String lastDirectory = mPreferences.get(LAST_SELECTED_DIRECTORY, null);
|
||||||
|
if(lastDirectory != null)
|
||||||
|
{
|
||||||
|
File file = new File(lastDirectory);
|
||||||
|
if(file.exists() && file.isDirectory())
|
||||||
|
{
|
||||||
|
fileChooser.setInitialDirectory(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileChooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("sdrtrunk bits recording", "*.bits"));
|
||||||
|
File selected = fileChooser.showOpenDialog(getScene().getWindow());
|
||||||
|
|
||||||
|
if(selected != null)
|
||||||
|
{
|
||||||
|
mLog.info("Last Selected Directory updated to [" + selected.getParent() +"]");
|
||||||
|
mPreferences.put(LAST_SELECTED_DIRECTORY, selected.getParent());
|
||||||
|
load(selected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSelectFileButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selected file path label.
|
||||||
|
*/
|
||||||
|
private Label getSelectedFileLabel()
|
||||||
|
{
|
||||||
|
if(mSelectedFileLabel == null)
|
||||||
|
{
|
||||||
|
mSelectedFileLabel = new Label(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSelectedFileLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check box to apply filter to show/hide TS0 messages
|
||||||
|
* @return check box control
|
||||||
|
*/
|
||||||
|
private CheckBox getShowTS0()
|
||||||
|
{
|
||||||
|
if(mShowTS0 == null)
|
||||||
|
{
|
||||||
|
mShowTS0 = new CheckBox("TS0");
|
||||||
|
mShowTS0.setSelected(true);
|
||||||
|
mShowTS0.setOnAction(event -> updateFilters());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mShowTS0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check box to apply filter to show/hide TS0 messages
|
||||||
|
* @return check box control
|
||||||
|
*/
|
||||||
|
private CheckBox getShowTS1()
|
||||||
|
{
|
||||||
|
if(mShowTS1 == null)
|
||||||
|
{
|
||||||
|
mShowTS1 = new CheckBox("TS1");
|
||||||
|
mShowTS1.setSelected(true);
|
||||||
|
mShowTS1.setOnAction(event -> updateFilters());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mShowTS1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check box to apply filter to show/hide TS0 messages
|
||||||
|
* @return check box control
|
||||||
|
*/
|
||||||
|
private CheckBox getShowTS2()
|
||||||
|
{
|
||||||
|
if(mShowTS2 == null)
|
||||||
|
{
|
||||||
|
mShowTS2 = new CheckBox("TS2");
|
||||||
|
mShowTS2.setSelected(true);
|
||||||
|
mShowTS2.setOnAction(event -> updateFilters());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mShowTS2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search text filter box
|
||||||
|
* @return text control for entering search text
|
||||||
|
*/
|
||||||
|
private TextField getSearchText()
|
||||||
|
{
|
||||||
|
if(mSearchText == null)
|
||||||
|
{
|
||||||
|
mSearchText = new TextField();
|
||||||
|
mSearchText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSearchText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text box for find text
|
||||||
|
*/
|
||||||
|
private TextField getFindText()
|
||||||
|
{
|
||||||
|
if(mFindText == null)
|
||||||
|
{
|
||||||
|
mFindText = new TextField();
|
||||||
|
mFindText.textProperty().addListener((observable, oldValue, newValue) -> updateFilters());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFindText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find button to search for the text in the find text box.
|
||||||
|
* @return button
|
||||||
|
*/
|
||||||
|
private Button getFindButton()
|
||||||
|
{
|
||||||
|
if(mFindButton == null)
|
||||||
|
{
|
||||||
|
mFindButton = new Button("Find");
|
||||||
|
mFindButton.setOnAction(event -> find(getFindText().getText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFindButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find next button to search for the text in the find text box.
|
||||||
|
* @return button
|
||||||
|
*/
|
||||||
|
private Button getFindNextButton()
|
||||||
|
{
|
||||||
|
if(mFindNextButton == null)
|
||||||
|
{
|
||||||
|
mFindNextButton = new Button("Next");
|
||||||
|
mFindNextButton.setOnAction(event -> findNext(getFindText().getText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mFindNextButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use Hytera Tier 3 Compressed Talkgroups
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private CheckBox getUseCompressedTalkgroups()
|
||||||
|
{
|
||||||
|
if(mUseCompressedTalkgroups == null)
|
||||||
|
{
|
||||||
|
mUseCompressedTalkgroups = new CheckBox("Use Compressed Talkgroups");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mUseCompressedTalkgroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
Application viewer = new Application()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void start(Stage primaryStage) throws Exception
|
||||||
|
{
|
||||||
|
Scene scene = new Scene(new DMRRecordingViewer(), 1100, 800);
|
||||||
|
primaryStage.setTitle("DMR Recording Viewer");
|
||||||
|
primaryStage.setScene(scene);
|
||||||
|
primaryStage.show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Runnable r = () -> {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
viewer.start(new Stage());
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error starting DMR recording viewer application", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Platform.startup(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General 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.gui.dmr;
|
||||||
|
|
||||||
|
import io.github.dsheirer.gui.JavaFxWindowRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request to show the DMR recording viewer
|
||||||
|
*/
|
||||||
|
public class ViewDmrRecordingViewerRequest extends JavaFxWindowRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public ViewDmrRecordingViewerRequest() {}
|
||||||
|
}
|
||||||
@@ -217,6 +217,9 @@ public class RadioIdEditor extends IdentifierEditor<Radio>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mLog.warn("Unable to find radio id editor for protocol [" + protocol + "] and format [" + integerFormat +
|
||||||
|
"] - using default editor");
|
||||||
|
|
||||||
//Use a default instance
|
//Use a default instance
|
||||||
for(RadioDetail detail: mRadioDetails)
|
for(RadioDetail detail: mRadioDetails)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ public class RadioIdRangeEditor extends IdentifierEditor<RadioRange>
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLog.warn("Couldn't find radio ID detail for protocol [" + getItem().getProtocol() +
|
mLog.warn("Couldn't find radio ID detail for protocol [" + getItem().getProtocol() +
|
||||||
"] and format [" + format + "]");
|
"] and format [" + format + "]");
|
||||||
getFormatLabel().setText(" ");
|
getFormatLabel().setText(" ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,6 +267,9 @@ public class RadioIdRangeEditor extends IdentifierEditor<RadioRange>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mLog.warn("Unable to find radio id range editor for protocol [" + protocol + "] and format [" + integerFormat +
|
||||||
|
"] - using default editor");
|
||||||
|
|
||||||
//Use a default instance
|
//Use a default instance
|
||||||
for(RadioIdDetail detail: mRadioDetails)
|
for(RadioIdDetail detail: mRadioDetails)
|
||||||
{
|
{
|
||||||
@@ -287,20 +290,23 @@ public class RadioIdRangeEditor extends IdentifierEditor<RadioRange>
|
|||||||
{
|
{
|
||||||
List<RadioIdDetail> details = new ArrayList<>();
|
List<RadioIdDetail> details = new ArrayList<>();
|
||||||
details.add(new RadioIdDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
|
details.add(new RadioIdDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
|
||||||
new IntegerFormatter(0,0xFFFFFF), "Format: 0 - 16777215"));
|
new IntegerFormatter(0,0xFFFFFF), "Format: 0 - 16777215"));
|
||||||
details.add(new RadioIdDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
|
details.add(new RadioIdDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
|
||||||
new HexFormatter(0,0xFFFFFF), "Format: 0 - FFFFFF"));
|
new HexFormatter(0,0xFFFFFF), "Format: 0 - FFFFFF"));
|
||||||
details.add(new RadioIdDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0x7FFFFF),
|
details.add(new RadioIdDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0x7FFFFF),
|
||||||
new IntegerFormatter(0,0x7FFFFF), "Format: 0 - 8388607"));
|
new IntegerFormatter(0,0x7FFFFF), "Format: 0 - 8388607"));
|
||||||
details.add(new RadioIdDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0x7FFFFF),
|
details.add(new RadioIdDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0x7FFFFF),
|
||||||
new HexFormatter(0,0x7FFFFF), "Format: 0 - 7FFFFF"));
|
new HexFormatter(0,0x7FFFFF), "Format: 0 - 7FFFFF"));
|
||||||
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
||||||
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
||||||
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
|
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
|
||||||
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
||||||
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
|
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
|
||||||
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
||||||
|
details.add(new RadioIdDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
||||||
|
new IntegerFormatter(0,16777215), "Format: 0 - 16777215"));
|
||||||
|
details.add(new RadioIdDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
|
||||||
|
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLog.warn("Couldn't find talkgroup detail for protocol [" + getItem().getProtocol() +
|
mLog.warn("Couldn't find talkgroup detail for protocol [" + getItem().getProtocol() +
|
||||||
"] and format [" + format + "]");
|
"] and format [" + format + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
getFormatLabel().setText(talkgroupDetail.getTooltip());
|
getFormatLabel().setText(talkgroupDetail.getTooltip());
|
||||||
@@ -219,6 +219,9 @@ public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mLog.warn("Unable to find talkgroup editor for protocol [" + protocol + "] and format [" + integerFormat +
|
||||||
|
"] - using default editor");
|
||||||
|
|
||||||
//Use a default instance
|
//Use a default instance
|
||||||
for(TalkgroupDetail detail: mTalkgroupDetails)
|
for(TalkgroupDetail detail: mTalkgroupDetails)
|
||||||
{
|
{
|
||||||
@@ -236,37 +239,37 @@ public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
|
|||||||
{
|
{
|
||||||
mTalkgroupDetails.clear();
|
mTalkgroupDetails.clear();
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
|
||||||
"Format: 0 - 65535"));
|
"Format: 0 - 65535"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
|
||||||
"Format: 0 - FFFF"));
|
"Format: 0 - FFFF"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(1,0xFFFFFF),
|
||||||
"Format: 0 - 16,777,215"));
|
"Format: 1 - 16,777,215"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(1,0xFFFFFF),
|
||||||
"Format: 0 - FFFFFF"));
|
"Format: 1 - FFFFFF"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.FLEETSYNC, IntegerFormat.FORMATTED,
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.FLEETSYNC, IntegerFormat.FORMATTED,
|
||||||
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
|
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.LTR, IntegerFormat.FORMATTED, new LtrFormatter(0,0x3FFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.LTR, IntegerFormat.FORMATTED, new LtrFormatter(0,0x3FFF),
|
||||||
"Format: A-HH-TTT = Area (0-1), Home (1-31), Talkgroup (1-255)"));
|
"Format: A-HH-TTT = Area (0-1), Home (1-31), Talkgroup (1-255)"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
||||||
"Format: 0 - 65535"));
|
"Format: 0 - 65535"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
||||||
"Format: 0 - FFFF"));
|
"Format: 0 - FFFF"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MPT1327, IntegerFormat.FORMATTED,
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MPT1327, IntegerFormat.FORMATTED,
|
||||||
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
|
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.DECIMAL, new IntegerFormatter(1,0xFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.DECIMAL, new IntegerFormatter(1,0xFFFF),
|
||||||
"Format: 1 - 65535"));
|
"Format: 1 - 65535"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.HEXADECIMAL, new HexFormatter(1,0xFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.NBFM, IntegerFormat.HEXADECIMAL, new HexFormatter(1,0xFFFF),
|
||||||
"Format: 1 - FFFF"));
|
"Format: 1 - FFFF"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
||||||
"Format: 0 - 65535"));
|
"Format: 0 - 65535"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
||||||
"Format: 0 - FFFF"));
|
"Format: 0 - FFFF"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
||||||
"Format: 0 - FFFFFF"));
|
"Format: 0 - FFFFFF"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
|
||||||
"Format: 0 - FFFFFF"));
|
"Format: 0 - FFFFFF"));
|
||||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
|
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
|
||||||
"Format: 0 - FFFFFF"));
|
"Format: 0 - FFFFFF"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class TalkgroupDetail
|
public static class TalkgroupDetail
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ public class TalkgroupRangeEditor extends IdentifierEditor<TalkgroupRange>
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
mLog.warn("Couldn't find talkgroup detail for protocol [" + getItem().getProtocol() +
|
mLog.warn("Couldn't find talkgroup detail for protocol [" + getItem().getProtocol() +
|
||||||
"] and format [" + format + "]");
|
"] and format [" + format + "]");
|
||||||
getFormatLabel().setText(" ");
|
getFormatLabel().setText(" ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,6 +269,9 @@ public class TalkgroupRangeEditor extends IdentifierEditor<TalkgroupRange>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mLog.warn("Unable to find talkgroup range editor for protocol [" + protocol + "] and format [" + integerFormat +
|
||||||
|
"] - using default editor");
|
||||||
|
|
||||||
//Use a default instance
|
//Use a default instance
|
||||||
for(TalkgroupDetail detail: mTalkgroupDetails)
|
for(TalkgroupDetail detail: mTalkgroupDetails)
|
||||||
{
|
{
|
||||||
@@ -289,31 +292,35 @@ public class TalkgroupRangeEditor extends IdentifierEditor<TalkgroupRange>
|
|||||||
{
|
{
|
||||||
List<TalkgroupDetail> details = new ArrayList<>();
|
List<TalkgroupDetail> details = new ArrayList<>();
|
||||||
details.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
|
details.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
|
||||||
new IntegerFormatter(0,65535), "Format: 0 - 65535"));
|
new IntegerFormatter(0,65535), "Format: 0 - 65535"));
|
||||||
details.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
|
details.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
|
||||||
new HexFormatter(0,65535), "Format: 0 - FFFF"));
|
new HexFormatter(0,65535), "Format: 0 - FFFF"));
|
||||||
details.add(new TalkgroupDetail(Protocol.FLEETSYNC, IntegerFormat.FORMATTED,
|
details.add(new TalkgroupDetail(Protocol.FLEETSYNC, IntegerFormat.FORMATTED,
|
||||||
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
|
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
|
||||||
"Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
|
"Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
|
||||||
details.add(new TalkgroupDetail(Protocol.LTR, IntegerFormat.FORMATTED, new LtrFormatter(0,0x3FFF),
|
details.add(new TalkgroupDetail(Protocol.LTR, IntegerFormat.FORMATTED, new LtrFormatter(0,0x3FFF),
|
||||||
new LtrFormatter(0,0x3FFF), "Format: A-HH-TTT = Area (0-1), Home (1-31), Talkgroup (1-255)"));
|
new LtrFormatter(0,0x3FFF), "Format: A-HH-TTT = Area (0-1), Home (1-31), Talkgroup (1-255)"));
|
||||||
details.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
details.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
||||||
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
|
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
|
||||||
details.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
details.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
||||||
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
|
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
|
||||||
details.add(new TalkgroupDetail(Protocol.MPT1327, IntegerFormat.FORMATTED,
|
details.add(new TalkgroupDetail(Protocol.MPT1327, IntegerFormat.FORMATTED,
|
||||||
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
|
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
|
||||||
"Format: PPP-IIII = Prefix (0-127), Ident (1-8191)"));
|
"Format: PPP-IIII = Prefix (0-127), Ident (1-8191)"));
|
||||||
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
|
||||||
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
|
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
|
||||||
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
|
||||||
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
|
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
|
||||||
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
|
||||||
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
new IntegerFormatter(0,16777215), "Format: 0 - 16777215"));
|
||||||
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
|
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
|
||||||
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
||||||
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
|
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
|
||||||
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
|
||||||
|
details.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(1,16777215),
|
||||||
|
new IntegerFormatter(1,16777215), "Format: 1 - 16777215"));
|
||||||
|
details.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(1,0XFFFFFF),
|
||||||
|
new HexFormatter(1, 0xFFFFFF), "Format: 0 - FFFFFF"));
|
||||||
|
|
||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -38,6 +38,8 @@ import io.github.dsheirer.record.RecorderType;
|
|||||||
import io.github.dsheirer.record.config.RecordConfiguration;
|
import io.github.dsheirer.record.config.RecordConfiguration;
|
||||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
@@ -65,9 +67,6 @@ import org.controlsfx.control.ToggleSwitch;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR channel configuration editor
|
* DMR channel configuration editor
|
||||||
*/
|
*/
|
||||||
@@ -83,6 +82,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
private RecordConfigurationEditor mRecordConfigurationEditor;
|
private RecordConfigurationEditor mRecordConfigurationEditor;
|
||||||
private ToggleSwitch mIgnoreDataCallsButton;
|
private ToggleSwitch mIgnoreDataCallsButton;
|
||||||
private ToggleSwitch mIgnoreCRCChecksumsButton;
|
private ToggleSwitch mIgnoreCRCChecksumsButton;
|
||||||
|
private ToggleSwitch mUseCompressedTalkgroupsToggle;
|
||||||
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
|
||||||
private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
|
private TableView<TimeslotFrequency> mTimeslotFrequencyTable;
|
||||||
private IntegerTextField mLogicalSlotNumberField;
|
private IntegerTextField mLogicalSlotNumberField;
|
||||||
@@ -164,6 +164,14 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
GridPane.setConstraints(ignoreCRCLabel, 5, row);
|
GridPane.setConstraints(ignoreCRCLabel, 5, row);
|
||||||
gridPane.getChildren().add(ignoreCRCLabel);
|
gridPane.getChildren().add(ignoreCRCLabel);
|
||||||
|
|
||||||
|
GridPane.setConstraints(getUseCompressedTalkgroupsToggle(), 6, row);
|
||||||
|
gridPane.getChildren().add(getUseCompressedTalkgroupsToggle());
|
||||||
|
|
||||||
|
Label useCompressedTalkgroupsLabel = new Label("Use Compressed Talkgroups");
|
||||||
|
GridPane.setHalignment(useCompressedTalkgroupsLabel, HPos.LEFT);
|
||||||
|
GridPane.setConstraints(useCompressedTalkgroupsLabel, 7, row);
|
||||||
|
gridPane.getChildren().add(useCompressedTalkgroupsLabel);
|
||||||
|
|
||||||
Label timeslotTableLabel = new Label("Logical Slot Number (LSN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies");
|
Label timeslotTableLabel = new Label("Logical Slot Number (LSN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies");
|
||||||
GridPane.setHalignment(timeslotTableLabel, HPos.LEFT);
|
GridPane.setHalignment(timeslotTableLabel, HPos.LEFT);
|
||||||
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 6, 1);
|
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 6, 1);
|
||||||
@@ -512,6 +520,24 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
return mIgnoreCRCChecksumsButton;
|
return mIgnoreCRCChecksumsButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use compressed talkgroups toggle switch. Let's the user select to turn on compressed talkgroups for Hytera
|
||||||
|
* Tier-III systems.
|
||||||
|
* @return toggle.
|
||||||
|
*/
|
||||||
|
private ToggleSwitch getUseCompressedTalkgroupsToggle()
|
||||||
|
{
|
||||||
|
if(mUseCompressedTalkgroupsToggle == null)
|
||||||
|
{
|
||||||
|
mUseCompressedTalkgroupsToggle = new ToggleSwitch();
|
||||||
|
mUseCompressedTalkgroupsToggle.setTooltip(new Tooltip("Use Compressed Talkgroups. This is only for Hytera Tier-III Trunked Systems"));
|
||||||
|
mUseCompressedTalkgroupsToggle.setDisable(true);
|
||||||
|
mUseCompressedTalkgroupsToggle.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mUseCompressedTalkgroupsToggle;
|
||||||
|
}
|
||||||
|
|
||||||
private Spinner<Integer> getTrafficChannelPoolSizeSpinner()
|
private Spinner<Integer> getTrafficChannelPoolSizeSpinner()
|
||||||
{
|
{
|
||||||
if(mTrafficChannelPoolSizeSpinner == null)
|
if(mTrafficChannelPoolSizeSpinner == null)
|
||||||
@@ -578,6 +604,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
{
|
{
|
||||||
getIgnoreCRCChecksumsButton().setDisable(config == null);
|
getIgnoreCRCChecksumsButton().setDisable(config == null);
|
||||||
getIgnoreDataCallsButton().setDisable(config == null);
|
getIgnoreDataCallsButton().setDisable(config == null);
|
||||||
|
getUseCompressedTalkgroupsToggle().setDisable(config == null);
|
||||||
getTrafficChannelPoolSizeSpinner().setDisable(config == null);
|
getTrafficChannelPoolSizeSpinner().setDisable(config == null);
|
||||||
getTimeslotTable().getItems().clear();
|
getTimeslotTable().getItems().clear();
|
||||||
getTimeslotTable().setDisable(config == null);
|
getTimeslotTable().setDisable(config == null);
|
||||||
@@ -597,6 +624,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
|
|
||||||
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
|
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
|
||||||
getIgnoreCRCChecksumsButton().setSelected(decodeConfig.getIgnoreCRCChecksums());
|
getIgnoreCRCChecksumsButton().setSelected(decodeConfig.getIgnoreCRCChecksums());
|
||||||
|
getUseCompressedTalkgroupsToggle().setSelected(decodeConfig.isUseCompressedTalkgroups());
|
||||||
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
|
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
|
||||||
|
|
||||||
for(TimeslotFrequency timeslotFrequency: decodeConfig.getTimeslotMap())
|
for(TimeslotFrequency timeslotFrequency: decodeConfig.getTimeslotMap())
|
||||||
@@ -608,6 +636,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
{
|
{
|
||||||
getIgnoreCRCChecksumsButton().setSelected(false);
|
getIgnoreCRCChecksumsButton().setSelected(false);
|
||||||
getIgnoreDataCallsButton().setSelected(false);
|
getIgnoreDataCallsButton().setSelected(false);
|
||||||
|
getUseCompressedTalkgroupsToggle().setSelected(false);
|
||||||
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0);
|
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0);
|
||||||
getChannelRotationDelaySpinner().getValueFactory().setValue(200);
|
getChannelRotationDelaySpinner().getValueFactory().setValue(200);
|
||||||
}
|
}
|
||||||
@@ -630,6 +659,7 @@ public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
|||||||
config.setIgnoreCRCChecksums(getIgnoreCRCChecksumsButton().isSelected());
|
config.setIgnoreCRCChecksums(getIgnoreCRCChecksumsButton().isSelected());
|
||||||
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
|
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
|
||||||
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
|
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
|
||||||
|
config.setUseCompressedTalkgroups(getUseCompressedTalkgroupsToggle().isSelected());
|
||||||
config.setTimeslotMap(new ArrayList<>(getTimeslotTable().getItems()));
|
config.setTimeslotMap(new ArrayList<>(getTimeslotTable().getItems()));
|
||||||
getItem().setDecodeConfiguration(config);
|
getItem().setDecodeConfiguration(config);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General 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.gui.power;
|
package io.github.dsheirer.gui.power;
|
||||||
|
|
||||||
import io.github.dsheirer.controller.channel.Channel;
|
import io.github.dsheirer.controller.channel.Channel;
|
||||||
|
import io.github.dsheirer.dsp.filter.channelizer.PolyphaseChannelSource;
|
||||||
import io.github.dsheirer.dsp.squelch.ISquelchConfiguration;
|
import io.github.dsheirer.dsp.squelch.ISquelchConfiguration;
|
||||||
import io.github.dsheirer.gui.control.DbPowerMeter;
|
import io.github.dsheirer.gui.control.DbPowerMeter;
|
||||||
import io.github.dsheirer.module.ProcessingChain;
|
import io.github.dsheirer.module.ProcessingChain;
|
||||||
import io.github.dsheirer.playlist.PlaylistManager;
|
import io.github.dsheirer.playlist.PlaylistManager;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.sample.complex.ComplexSamplesToNativeBufferModule;
|
||||||
|
import io.github.dsheirer.settings.SettingsManager;
|
||||||
|
import io.github.dsheirer.source.Source;
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
|
import io.github.dsheirer.source.tuner.channel.HalfBandTunerChannelSource;
|
||||||
|
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||||
|
import io.github.dsheirer.source.tuner.channel.TunerChannelSource;
|
||||||
|
import io.github.dsheirer.spectrum.ComplexDftProcessor;
|
||||||
|
import io.github.dsheirer.spectrum.FrequencyOverlayPanel;
|
||||||
|
import io.github.dsheirer.spectrum.SpectrumPanel;
|
||||||
|
import io.github.dsheirer.spectrum.converter.ComplexDecibelConverter;
|
||||||
|
import io.github.dsheirer.spectrum.converter.DFTResultsConverter;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.event.ComponentEvent;
|
||||||
|
import java.awt.event.ComponentListener;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseWheelEvent;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.List;
|
||||||
import jiconfont.icons.font_awesome.FontAwesome;
|
import jiconfont.icons.font_awesome.FontAwesome;
|
||||||
import jiconfont.swing.IconFontSwing;
|
import jiconfont.swing.IconFontSwing;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
@@ -16,9 +56,12 @@ import org.slf4j.LoggerFactory;
|
|||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JLayeredPane;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import java.awt.EventQueue;
|
import javax.swing.JSeparator;
|
||||||
import java.text.DecimalFormat;
|
import javax.swing.JSpinner;
|
||||||
|
import javax.swing.SpinnerNumberModel;
|
||||||
|
import javax.swing.event.MouseInputAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display for channel power and squelch details
|
* Display for channel power and squelch details
|
||||||
@@ -27,28 +70,42 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
|
|||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(ChannelPowerPanel.class);
|
private static final Logger mLog = LoggerFactory.getLogger(ChannelPowerPanel.class);
|
||||||
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0");
|
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0.0");
|
||||||
|
private static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
|
||||||
private static final String NOT_AVAILABLE = "Not Available";
|
private static final String NOT_AVAILABLE = "Not Available";
|
||||||
private PlaylistManager mPlaylistManager;
|
private PlaylistManager mPlaylistManager;
|
||||||
private ProcessingChain mProcessingChain;
|
private ProcessingChain mProcessingChain;
|
||||||
|
private ComplexSamplesToNativeBufferModule mSampleStreamTapModule = new ComplexSamplesToNativeBufferModule();
|
||||||
|
private ComplexDftProcessor mComplexDftProcessor = new ComplexDftProcessor();
|
||||||
|
private DFTResultsConverter mDFTResultsConverter = new ComplexDecibelConverter();
|
||||||
|
private JLayeredPane mLayeredPanel;
|
||||||
|
private SpectrumPanel mSpectrumPanel;
|
||||||
|
private FrequencyOverlayPanel mFrequencyOverlayPanel;
|
||||||
private DbPowerMeter mPowerMeter = new DbPowerMeter();
|
private DbPowerMeter mPowerMeter = new DbPowerMeter();
|
||||||
private PeakMonitor mPeakMonitor = new PeakMonitor(DbPowerMeter.DEFAULT_MINIMUM_POWER);
|
private PeakMonitor mPeakMonitor = new PeakMonitor(DbPowerMeter.DEFAULT_MINIMUM_POWER);
|
||||||
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
|
private SourceEventProcessor mSourceEventProcessor = new SourceEventProcessor();
|
||||||
|
private SpinnerNumberModel mNoiseFloorSpinnerModel;
|
||||||
|
private JSpinner mNoiseFloorSpinner;
|
||||||
private JLabel mPowerLabel;
|
private JLabel mPowerLabel;
|
||||||
private JLabel mPeakLabel;
|
private JLabel mPeakLabel;
|
||||||
private JLabel mSquelchLabel;
|
private JLabel mSquelchLabel;
|
||||||
private JLabel mSquelchValueLabel;
|
private JLabel mSquelchValueLabel;
|
||||||
|
private JLabel mPllFrequencyLabel;
|
||||||
|
private JLabel mPllFrequencyValueLabel;
|
||||||
private JButton mSquelchUpButton;
|
private JButton mSquelchUpButton;
|
||||||
private JButton mSquelchDownButton;
|
private JButton mSquelchDownButton;
|
||||||
|
private JButton mLogIndexesButton;
|
||||||
private double mSquelchThreshold;
|
private double mSquelchThreshold;
|
||||||
|
private boolean mPanelVisible = false;
|
||||||
|
private boolean mDftProcessing = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance.
|
* Constructs an instance.
|
||||||
*/
|
*/
|
||||||
public ChannelPowerPanel(PlaylistManager playlistManager)
|
public ChannelPowerPanel(PlaylistManager playlistManager, SettingsManager settingsManager)
|
||||||
{
|
{
|
||||||
mPlaylistManager = playlistManager;
|
mPlaylistManager = playlistManager;
|
||||||
|
|
||||||
setLayout(new MigLayout("", "[][grow,fill]", "[grow,fill]"));
|
setLayout(new MigLayout("", "[][][][grow,fill]", "[grow,fill]"));
|
||||||
mPowerMeter.setPeakVisible(true);
|
mPowerMeter.setPeakVisible(true);
|
||||||
mPowerMeter.setSquelchThresholdVisible(true);
|
mPowerMeter.setSquelchThresholdVisible(true);
|
||||||
add(mPowerMeter);
|
add(mPowerMeter);
|
||||||
@@ -85,6 +142,184 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
|
|||||||
valuePanel.add(mSquelchDownButton);
|
valuePanel.add(mSquelchDownButton);
|
||||||
|
|
||||||
add(valuePanel);
|
add(valuePanel);
|
||||||
|
add(new JSeparator(JSeparator.VERTICAL));
|
||||||
|
|
||||||
|
JPanel fftPanel = new JPanel();
|
||||||
|
fftPanel.setLayout(new MigLayout("insets 0", "[grow,fill]", "[][grow,fill]"));
|
||||||
|
|
||||||
|
JPanel labelPanel = new JPanel();
|
||||||
|
labelPanel.setLayout(new MigLayout("insets 0", "[grow,fill][right][grow,fill][right][grow,fill][]", ""));
|
||||||
|
labelPanel.add(new JLabel("Channel Spectrum"));
|
||||||
|
|
||||||
|
mPllFrequencyLabel = new JLabel("PLL:");
|
||||||
|
mPllFrequencyLabel.setEnabled(false);
|
||||||
|
labelPanel.add(mPllFrequencyLabel);
|
||||||
|
mPllFrequencyValueLabel = new JLabel("0 Hz");
|
||||||
|
mPllFrequencyValueLabel.setEnabled(false);
|
||||||
|
labelPanel.add(mPllFrequencyValueLabel);
|
||||||
|
|
||||||
|
mNoiseFloorSpinnerModel = new SpinnerNumberModel(18, 8, 36, 1);
|
||||||
|
mNoiseFloorSpinnerModel.addChangeListener(e -> {
|
||||||
|
Number number = mNoiseFloorSpinnerModel.getNumber();
|
||||||
|
mSpectrumPanel.setSampleSize(number.doubleValue());
|
||||||
|
});
|
||||||
|
mNoiseFloorSpinner = new JSpinner(mNoiseFloorSpinnerModel);
|
||||||
|
labelPanel.add(mNoiseFloorSpinner);
|
||||||
|
labelPanel.add(new JLabel("Noise Floor"));
|
||||||
|
|
||||||
|
mLogIndexesButton = new JButton("Log Settings");
|
||||||
|
mLogIndexesButton.addActionListener(e -> {
|
||||||
|
if(mProcessingChain != null)
|
||||||
|
{
|
||||||
|
Source source = mProcessingChain.getSource();
|
||||||
|
|
||||||
|
if(source instanceof PolyphaseChannelSource pcs)
|
||||||
|
{
|
||||||
|
List<Integer> indexes = pcs.getOutputProcessorIndexes();
|
||||||
|
double sampleRate = pcs.getSampleRate();
|
||||||
|
long indexCenterFrequency = pcs.getIndexCenterFrequency();
|
||||||
|
long appliedFrequencyOffset = pcs.getFrequencyOffset();
|
||||||
|
long requestedCenterFrequency = pcs.getFrequency();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Polyphase Channel - BW: ").append(FREQUENCY_FORMAT.format(sampleRate / 1E6d));
|
||||||
|
sb.append(" Center/Requested/Mixer: ").append(FREQUENCY_FORMAT.format(indexCenterFrequency / 1E6d));
|
||||||
|
sb.append("/").append(FREQUENCY_FORMAT.format(requestedCenterFrequency / 1E6d));
|
||||||
|
sb.append("/").append(FREQUENCY_FORMAT.format(appliedFrequencyOffset / 1E6d));
|
||||||
|
sb.append(" Polyphase Indexes: ").append(indexes);
|
||||||
|
sb.append(" Tuner SR:").append(FREQUENCY_FORMAT.format(pcs.getTunerSampleRate() / 1E6d));
|
||||||
|
sb.append(" CF:").append(FREQUENCY_FORMAT.format(pcs.getTunerCenterFrequency() / 1E6d));
|
||||||
|
mLog.info(sb.toString());
|
||||||
|
mLog.info("Output Processor: " + pcs.getStateDescription());
|
||||||
|
}
|
||||||
|
else if(source instanceof HalfBandTunerChannelSource<?> hbtcs)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Heterodyne Channel - CF:").append(FREQUENCY_FORMAT.format(hbtcs.getFrequency() / 1E6d));
|
||||||
|
sb.append(" SR:").append(FREQUENCY_FORMAT.format(hbtcs.getSampleRate() / 1E6d));
|
||||||
|
sb.append(" Mixer:").append(FREQUENCY_FORMAT.format(hbtcs.getMixerFrequency() / 1E6d));
|
||||||
|
mLog.info(sb.toString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.info("Unsupported channel type: " + (source != null ? source.getClass() : " null"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
labelPanel.add(mLogIndexesButton);
|
||||||
|
|
||||||
|
fftPanel.add(labelPanel, "wrap");
|
||||||
|
|
||||||
|
mFrequencyOverlayPanel = new FrequencyOverlayPanel(settingsManager);
|
||||||
|
|
||||||
|
mSpectrumPanel = new SpectrumPanel(settingsManager);
|
||||||
|
mSpectrumPanel.setSampleSize(18.0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layered pane holds the overlapping spectrum and channel panels
|
||||||
|
* and manages the sizing of each panel with the resize listener
|
||||||
|
*/
|
||||||
|
mLayeredPanel = new JLayeredPane();
|
||||||
|
mLayeredPanel.addComponentListener(new ResizeListener());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a mouse adapter to handle mouse events over the spectrum
|
||||||
|
* and waterfall panels
|
||||||
|
*/
|
||||||
|
MouseEventProcessor mouser = new MouseEventProcessor();
|
||||||
|
|
||||||
|
mFrequencyOverlayPanel.addMouseListener(mouser);
|
||||||
|
mFrequencyOverlayPanel.addMouseMotionListener(mouser);
|
||||||
|
mFrequencyOverlayPanel.addMouseWheelListener(mouser);
|
||||||
|
|
||||||
|
//Add the spectrum and channel panels to the layered panel
|
||||||
|
mLayeredPanel.add(mSpectrumPanel, 0, 0);
|
||||||
|
mLayeredPanel.add(mFrequencyOverlayPanel, 1, 0);
|
||||||
|
|
||||||
|
fftPanel.add(mLayeredPanel);
|
||||||
|
add(fftPanel);
|
||||||
|
|
||||||
|
mSampleStreamTapModule.setListener(mComplexDftProcessor);
|
||||||
|
mComplexDftProcessor.addConverter(mDFTResultsConverter);
|
||||||
|
mDFTResultsConverter.addListener(mSpectrumPanel);
|
||||||
|
mSpectrumPanel.clearSpectrum();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals this panel to indicate if this panel is visible to turn on the FFT processor when the panel is visible
|
||||||
|
* and turn off the FFT processor when it's not.
|
||||||
|
*
|
||||||
|
* Note: this method is intended to be called by the Swing event thread to ensure that only a single thread is
|
||||||
|
* invoking either this method, or the receive() method, since there is no thread synchronization between these
|
||||||
|
* two methods and they each depend on stable access to the mPanelVisible variable.
|
||||||
|
*
|
||||||
|
* @param visible true to indicate that this panel is showing/visible.
|
||||||
|
*/
|
||||||
|
public void setPanelVisible(boolean visible)
|
||||||
|
{
|
||||||
|
mPanelVisible = visible;
|
||||||
|
updateFFTProcessing();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates processing state for the DFT processor. Turns on DFT processing when we have a processing chain and
|
||||||
|
* when the user has this tab selected and visible. Otherwise, turns off DFT processing.
|
||||||
|
*/
|
||||||
|
private void updateFFTProcessing()
|
||||||
|
{
|
||||||
|
if(mPanelVisible && mProcessingChain != null)
|
||||||
|
{
|
||||||
|
startDftProcessing();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stopDftProcessing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts DFT processing
|
||||||
|
*/
|
||||||
|
private void startDftProcessing()
|
||||||
|
{
|
||||||
|
if(!mDftProcessing)
|
||||||
|
{
|
||||||
|
mDftProcessing = true;
|
||||||
|
mSampleStreamTapModule.setListener(mComplexDftProcessor);
|
||||||
|
mComplexDftProcessor.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops DFT processing
|
||||||
|
*/
|
||||||
|
private void stopDftProcessing()
|
||||||
|
{
|
||||||
|
if(mDftProcessing)
|
||||||
|
{
|
||||||
|
mSampleStreamTapModule.removeListener();
|
||||||
|
mComplexDftProcessor.stop();
|
||||||
|
mSpectrumPanel.clearSpectrum();
|
||||||
|
mDftProcessing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the decoder's PLL frequency on the swing dispatch thread
|
||||||
|
* @param trackingFrequency that is currently measured.
|
||||||
|
*/
|
||||||
|
private void updatePllFrequency(long trackingFrequency)
|
||||||
|
{
|
||||||
|
//Note: we flip the sign on the error measurement because the value represents the amount of offset the PLL
|
||||||
|
//has to apply to move the signal to center/baseband
|
||||||
|
EventQueue.invokeLater(() -> {
|
||||||
|
String formattedValue = NumberFormat.getInstance().format(-trackingFrequency);
|
||||||
|
mPllFrequencyValueLabel.setText(formattedValue + " Hz");
|
||||||
|
mPllFrequencyValueLabel.setEnabled(true);
|
||||||
|
mPllFrequencyLabel.setEnabled(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
mFrequencyOverlayPanel.setPllTrackingFrequency(trackingFrequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,6 +359,13 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
|
|||||||
|
|
||||||
mPeakLabel.setText("0");
|
mPeakLabel.setText("0");
|
||||||
mPowerLabel.setText("0");
|
mPowerLabel.setText("0");
|
||||||
|
mPllFrequencyLabel.setEnabled(false);
|
||||||
|
mPllFrequencyValueLabel.setText("0 Hz");
|
||||||
|
mPllFrequencyValueLabel.setEnabled(false);
|
||||||
|
mFrequencyOverlayPanel.process(SourceEvent.frequencyChange(null, 0));
|
||||||
|
mFrequencyOverlayPanel.process(SourceEvent.sampleRateChange(0));
|
||||||
|
mFrequencyOverlayPanel.setPllTrackingFrequency(0);
|
||||||
|
mFrequencyOverlayPanel.setChannelBandwidth(0);
|
||||||
|
|
||||||
mSquelchLabel.setEnabled(false);
|
mSquelchLabel.setEnabled(false);
|
||||||
mSquelchValueLabel.setText("Not Available");
|
mSquelchValueLabel.setText("Not Available");
|
||||||
@@ -138,9 +380,11 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
|
|||||||
@Override
|
@Override
|
||||||
public void receive(ProcessingChain processingChain)
|
public void receive(ProcessingChain processingChain)
|
||||||
{
|
{
|
||||||
|
//Disconnect the FFT panel
|
||||||
if(mProcessingChain != null)
|
if(mProcessingChain != null)
|
||||||
{
|
{
|
||||||
mProcessingChain.removeSourceEventListener(mSourceEventProcessor);
|
mProcessingChain.removeSourceEventListener(mSourceEventProcessor);
|
||||||
|
mProcessingChain.removeModule(mSampleStreamTapModule);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Invoking reset - we're on the Swing dispatch thread here
|
//Invoking reset - we're on the Swing dispatch thread here
|
||||||
@@ -151,11 +395,35 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
|
|||||||
if(mProcessingChain != null)
|
if(mProcessingChain != null)
|
||||||
{
|
{
|
||||||
mProcessingChain.addSourceEventListener(mSourceEventProcessor);
|
mProcessingChain.addSourceEventListener(mSourceEventProcessor);
|
||||||
|
mProcessingChain.addModule(mSampleStreamTapModule);
|
||||||
|
|
||||||
|
Source source = mProcessingChain.getSource();
|
||||||
|
|
||||||
|
if(source instanceof TunerChannelSource tcs)
|
||||||
|
{
|
||||||
|
mFrequencyOverlayPanel.process(SourceEvent.frequencyChange(null, tcs.getFrequency()));
|
||||||
|
mFrequencyOverlayPanel.process(SourceEvent.sampleRateChange(tcs.getSampleRate()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Channel channel = mPlaylistManager.getChannelProcessingManager().getChannel(mProcessingChain);
|
||||||
|
|
||||||
|
if(channel != null)
|
||||||
|
{
|
||||||
|
TunerChannel tunerChannel = channel.getTunerChannel();
|
||||||
|
|
||||||
|
if(tunerChannel != null)
|
||||||
|
{
|
||||||
|
mFrequencyOverlayPanel.setChannelBandwidth(tunerChannel.getBandwidth());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFFTProcessing();
|
||||||
|
|
||||||
broadcast(SourceEvent.requestCurrentSquelchThreshold(null));
|
broadcast(SourceEvent.requestCurrentSquelchThreshold(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processor for source event stream to capture power level and squelch related source events.
|
* Processor for source event stream to capture power level and squelch related source events.
|
||||||
*/
|
*/
|
||||||
@@ -171,17 +439,12 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
|
|||||||
final double power = sourceEvent.getValue().doubleValue();
|
final double power = sourceEvent.getValue().doubleValue();
|
||||||
final double peak = mPeakMonitor.process(power);
|
final double peak = mPeakMonitor.process(power);
|
||||||
|
|
||||||
EventQueue.invokeLater(new Runnable()
|
EventQueue.invokeLater(() -> {
|
||||||
{
|
mPowerMeter.setPower(power);
|
||||||
@Override
|
mPowerLabel.setText(DECIMAL_FORMAT.format(power));
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
mPowerMeter.setPower(power);
|
|
||||||
mPowerLabel.setText(DECIMAL_FORMAT.format(power));
|
|
||||||
|
|
||||||
mPowerMeter.setPeak(peak);
|
mPowerMeter.setPeak(peak);
|
||||||
mPeakLabel.setText(DECIMAL_FORMAT.format(peak));
|
mPeakLabel.setText(DECIMAL_FORMAT.format(peak));
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -205,7 +468,70 @@ public class ChannelPowerPanel extends JPanel implements Listener<ProcessingChai
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
case NOTIFICATION_PLL_FREQUENCY ->
|
||||||
|
{
|
||||||
|
if(sourceEvent.hasValue())
|
||||||
|
{
|
||||||
|
updatePllFrequency(sourceEvent.getValue().longValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors the sizing of the layered pane and resizes the spectrum and
|
||||||
|
* channel panels whenever the layered pane is resized
|
||||||
|
*/
|
||||||
|
public class ResizeListener implements ComponentListener
|
||||||
|
{
|
||||||
|
@Override public void componentResized(ComponentEvent e)
|
||||||
|
{
|
||||||
|
Component c = e.getComponent();
|
||||||
|
|
||||||
|
mSpectrumPanel.setBounds(0, 0, c.getWidth(), c.getHeight());
|
||||||
|
mFrequencyOverlayPanel.setBounds(0, 0, c.getWidth(), c.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void componentHidden(ComponentEvent arg0) {}
|
||||||
|
@Override public void componentMoved(ComponentEvent arg0) {}
|
||||||
|
@Override public void componentShown(ComponentEvent arg0) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mouse event handler for the spectral display panel.
|
||||||
|
*/
|
||||||
|
public class MouseEventProcessor extends MouseInputAdapter
|
||||||
|
{
|
||||||
|
public MouseEventProcessor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void mouseWheelMoved(MouseWheelEvent e) {}
|
||||||
|
@Override public void mouseMoved(MouseEvent event)
|
||||||
|
{
|
||||||
|
update(event);
|
||||||
|
}
|
||||||
|
@Override public void mouseDragged(MouseEvent event) {}
|
||||||
|
@Override public void mousePressed(MouseEvent e) {}
|
||||||
|
@Override public void mouseClicked(MouseEvent event) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the cursor display while the mouse is performing actions
|
||||||
|
*/
|
||||||
|
private void update(MouseEvent event)
|
||||||
|
{
|
||||||
|
mFrequencyOverlayPanel.setCursorLocation(event.getPoint());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void mouseEntered(MouseEvent e)
|
||||||
|
{
|
||||||
|
mFrequencyOverlayPanel.setCursorVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void mouseExited(MouseEvent e)
|
||||||
|
{
|
||||||
|
mFrequencyOverlayPanel.setCursorVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.gui.preference;
|
package io.github.dsheirer.gui.preference;
|
||||||
|
|
||||||
|
import io.github.dsheirer.gui.preference.application.ApplicationPreferenceEditor;
|
||||||
import io.github.dsheirer.gui.preference.calibration.VectorCalibrationPreferenceEditor;
|
import io.github.dsheirer.gui.preference.calibration.VectorCalibrationPreferenceEditor;
|
||||||
import io.github.dsheirer.gui.preference.decoder.JmbeLibraryPreferenceEditor;
|
import io.github.dsheirer.gui.preference.decoder.JmbeLibraryPreferenceEditor;
|
||||||
import io.github.dsheirer.gui.preference.directory.DirectoryPreferenceEditor;
|
import io.github.dsheirer.gui.preference.directory.DirectoryPreferenceEditor;
|
||||||
@@ -39,6 +40,8 @@ public class PreferenceEditorFactory
|
|||||||
{
|
{
|
||||||
switch(preferenceEditorType)
|
switch(preferenceEditorType)
|
||||||
{
|
{
|
||||||
|
case APPLICATION:
|
||||||
|
return new ApplicationPreferenceEditor(userPreferences);
|
||||||
case AUDIO_DUPLICATE_CALL_DETECTION:
|
case AUDIO_DUPLICATE_CALL_DETECTION:
|
||||||
return new DuplicateCallPreferenceEditor(userPreferences);
|
return new DuplicateCallPreferenceEditor(userPreferences);
|
||||||
case AUDIO_MP3:
|
case AUDIO_MP3:
|
||||||
@@ -53,7 +56,7 @@ public class PreferenceEditorFactory
|
|||||||
return new DirectoryPreferenceEditor(userPreferences);
|
return new DirectoryPreferenceEditor(userPreferences);
|
||||||
case JMBE_LIBRARY:
|
case JMBE_LIBRARY:
|
||||||
return new JmbeLibraryPreferenceEditor(userPreferences);
|
return new JmbeLibraryPreferenceEditor(userPreferences);
|
||||||
case SOURCE_TUNER_CHANNELIZER:
|
case SOURCE_TUNERS:
|
||||||
return new TunerPreferenceEditor(userPreferences);
|
return new TunerPreferenceEditor(userPreferences);
|
||||||
case TALKGROUP_FORMAT:
|
case TALKGROUP_FORMAT:
|
||||||
return new TalkgroupFormatPreferenceEditor(userPreferences);
|
return new TalkgroupFormatPreferenceEditor(userPreferences);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,6 +24,7 @@ package io.github.dsheirer.gui.preference;
|
|||||||
*/
|
*/
|
||||||
public enum PreferenceEditorType
|
public enum PreferenceEditorType
|
||||||
{
|
{
|
||||||
|
APPLICATION("Application"),
|
||||||
CHANNEL_EVENT("Channel Events"),
|
CHANNEL_EVENT("Channel Events"),
|
||||||
DIRECTORY("Directories"),
|
DIRECTORY("Directories"),
|
||||||
JMBE_LIBRARY("JMBE Audio Library"),
|
JMBE_LIBRARY("JMBE Audio Library"),
|
||||||
@@ -31,7 +32,7 @@ public enum PreferenceEditorType
|
|||||||
AUDIO_RECORD("Record"),
|
AUDIO_RECORD("Record"),
|
||||||
AUDIO_OUTPUT("Output/Tones"),
|
AUDIO_OUTPUT("Output/Tones"),
|
||||||
AUDIO_DUPLICATE_CALL_DETECTION("Duplicate Calls"),
|
AUDIO_DUPLICATE_CALL_DETECTION("Duplicate Calls"),
|
||||||
SOURCE_TUNER_CHANNELIZER("Tuner Channelizer"),
|
SOURCE_TUNERS("Tuners"),
|
||||||
TALKGROUP_FORMAT("Talkgroup & Radio ID"),
|
TALKGROUP_FORMAT("Talkgroup & Radio ID"),
|
||||||
VECTOR_CALIBRATION("Vector Calibration"),
|
VECTOR_CALIBRATION("Vector Calibration"),
|
||||||
DEFAULT("Default");
|
DEFAULT("Default");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -22,6 +22,8 @@ package io.github.dsheirer.gui.preference;
|
|||||||
import io.github.dsheirer.eventbus.MyEventBus;
|
import io.github.dsheirer.eventbus.MyEventBus;
|
||||||
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
|
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
@@ -44,9 +46,6 @@ import javafx.stage.Stage;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.EnumMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preferences editor dialog
|
* Preferences editor dialog
|
||||||
*/
|
*/
|
||||||
@@ -166,6 +165,11 @@ public class UserPreferencesEditor extends BorderPane
|
|||||||
{
|
{
|
||||||
TreeItem<String> treeRoot = new TreeItem<>("Root node");
|
TreeItem<String> treeRoot = new TreeItem<>("Root node");
|
||||||
|
|
||||||
|
TreeItem<String> applicationItem = new TreeItem<>("Application");
|
||||||
|
applicationItem.getChildren().add(new TreeItem(PreferenceEditorType.APPLICATION));
|
||||||
|
treeRoot.getChildren().add(applicationItem);
|
||||||
|
applicationItem.setExpanded(true);
|
||||||
|
|
||||||
TreeItem<String> audioItem = new TreeItem<>("Audio");
|
TreeItem<String> audioItem = new TreeItem<>("Audio");
|
||||||
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_DUPLICATE_CALL_DETECTION));
|
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_DUPLICATE_CALL_DETECTION));
|
||||||
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_MP3));
|
audioItem.getChildren().add(new TreeItem(PreferenceEditorType.AUDIO_MP3));
|
||||||
@@ -196,7 +200,7 @@ public class UserPreferencesEditor extends BorderPane
|
|||||||
storageItem.setExpanded(true);
|
storageItem.setExpanded(true);
|
||||||
|
|
||||||
TreeItem<String> sourceItem = new TreeItem<>("Source");
|
TreeItem<String> sourceItem = new TreeItem<>("Source");
|
||||||
sourceItem.getChildren().add(new TreeItem(PreferenceEditorType.SOURCE_TUNER_CHANNELIZER));
|
sourceItem.getChildren().add(new TreeItem(PreferenceEditorType.SOURCE_TUNERS));
|
||||||
treeRoot.getChildren().add(sourceItem);
|
treeRoot.getChildren().add(sourceItem);
|
||||||
sourceItem.setExpanded(true);
|
sourceItem.setExpanded(true);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General 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.gui.preference.application;
|
||||||
|
|
||||||
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
|
import io.github.dsheirer.preference.application.ApplicationPreference;
|
||||||
|
import javafx.geometry.HPos;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Spinner;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preference settings for application
|
||||||
|
*/
|
||||||
|
public class ApplicationPreferenceEditor extends HBox
|
||||||
|
{
|
||||||
|
private ApplicationPreference mApplicationPreference;
|
||||||
|
private GridPane mEditorPane;
|
||||||
|
private Label mAutoStartTimeoutLabel;
|
||||||
|
private Spinner<Integer> mTimeoutSpinner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param userPreferences for obtaining reference to preference.
|
||||||
|
*/
|
||||||
|
public ApplicationPreferenceEditor(UserPreferences userPreferences)
|
||||||
|
{
|
||||||
|
mApplicationPreference = userPreferences.getApplicationPreference();
|
||||||
|
getChildren().add(getEditorPane());
|
||||||
|
}
|
||||||
|
|
||||||
|
private GridPane getEditorPane()
|
||||||
|
{
|
||||||
|
if(mEditorPane == null)
|
||||||
|
{
|
||||||
|
int row = 0;
|
||||||
|
mEditorPane = new GridPane();
|
||||||
|
mEditorPane.setVgap(10);
|
||||||
|
mEditorPane.setHgap(10);
|
||||||
|
mEditorPane.setPadding(new Insets(10, 10, 10, 10));
|
||||||
|
GridPane.setHalignment(getAutoStartTimeoutLabel(), HPos.RIGHT);
|
||||||
|
mEditorPane.add(getAutoStartTimeoutLabel(), 0, row);
|
||||||
|
mEditorPane.add(getTimeoutSpinner(), 1, row);
|
||||||
|
mEditorPane.add(new Label("seconds"), 2, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mEditorPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Label getAutoStartTimeoutLabel()
|
||||||
|
{
|
||||||
|
if(mAutoStartTimeoutLabel == null)
|
||||||
|
{
|
||||||
|
mAutoStartTimeoutLabel = new Label("Channel Auto-Start Timeout");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mAutoStartTimeoutLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spinner to select channel auto-start timeout value in range 0-30 seconds.
|
||||||
|
* @return spinner
|
||||||
|
*/
|
||||||
|
private Spinner<Integer> getTimeoutSpinner()
|
||||||
|
{
|
||||||
|
if(mTimeoutSpinner == null)
|
||||||
|
{
|
||||||
|
mTimeoutSpinner = new Spinner<>(0, 30, mApplicationPreference.getChannelAutoStartTimeout(), 1);
|
||||||
|
mTimeoutSpinner.valueProperty().addListener((observable, oldValue, newValue) -> mApplicationPreference.setChannelAutoStartTimeout(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTimeoutSpinner;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,10 +24,9 @@ import io.github.dsheirer.preference.UserPreferences;
|
|||||||
import io.github.dsheirer.preference.playback.PlaybackPreference;
|
import io.github.dsheirer.preference.playback.PlaybackPreference;
|
||||||
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
|
import io.github.dsheirer.source.mixer.MixerChannelConfiguration;
|
||||||
import io.github.dsheirer.source.mixer.MixerManager;
|
import io.github.dsheirer.source.mixer.MixerManager;
|
||||||
import javafx.beans.value.ChangeListener;
|
import java.nio.ByteBuffer;
|
||||||
import javafx.beans.value.ObservableValue;
|
import java.nio.ByteOrder;
|
||||||
import javafx.event.ActionEvent;
|
import java.nio.ShortBuffer;
|
||||||
import javafx.event.EventHandler;
|
|
||||||
import javafx.geometry.HPos;
|
import javafx.geometry.HPos;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Orientation;
|
import javafx.geometry.Orientation;
|
||||||
@@ -49,9 +48,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
import javax.sound.sampled.AudioSystem;
|
import javax.sound.sampled.AudioSystem;
|
||||||
import javax.sound.sampled.Clip;
|
import javax.sound.sampled.Clip;
|
||||||
import javax.sound.sampled.DataLine;
|
import javax.sound.sampled.DataLine;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.nio.ShortBuffer;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,20 +56,16 @@ import java.nio.ShortBuffer;
|
|||||||
public class PlaybackPreferenceEditor extends HBox
|
public class PlaybackPreferenceEditor extends HBox
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(PlaybackPreferenceEditor.class);
|
private final static Logger mLog = LoggerFactory.getLogger(PlaybackPreferenceEditor.class);
|
||||||
private PlaybackPreference mPlaybackPreference;
|
private final PlaybackPreference mPlaybackPreference;
|
||||||
private GridPane mEditorPane;
|
private GridPane mEditorPane;
|
||||||
private ComboBox<MixerChannelConfiguration> mMixerComboBox;
|
private ComboBox<MixerChannelConfiguration> mMixerComboBox;
|
||||||
private Button mMixerTestButton;
|
private Button mMixerTestButton;
|
||||||
private ToggleSwitch mUseAudioSegmentStartToneSwitch;
|
private ToggleSwitch mUseAudioSegmentStartToneSwitch;
|
||||||
private Button mTestStartToneButton;
|
private Button mTestStartToneButton;
|
||||||
private ToggleSwitch mUseAudioSegmentPreemptToneSwitch;
|
|
||||||
private Button mTestPreemptToneButton;
|
|
||||||
private ToggleSwitch mUseAudioSegmentDropToneSwitch;
|
private ToggleSwitch mUseAudioSegmentDropToneSwitch;
|
||||||
private Button mTestDropToneButton;
|
private Button mTestDropToneButton;
|
||||||
private ComboBox<ToneFrequency> mStartToneFrequencyComboBox;
|
private ComboBox<ToneFrequency> mStartToneFrequencyComboBox;
|
||||||
private ComboBox<ToneVolume> mStartToneVolumeComboBox;
|
private ComboBox<ToneVolume> mStartToneVolumeComboBox;
|
||||||
private ComboBox<ToneFrequency> mPreemptToneFrequencyComboBox;
|
|
||||||
private ComboBox<ToneVolume> mPreemptToneVolumeComboBox;
|
|
||||||
private ComboBox<ToneFrequency> mDropToneFrequencyComboBox;
|
private ComboBox<ToneFrequency> mDropToneFrequencyComboBox;
|
||||||
private ComboBox<ToneVolume> mDropToneVolumeComboBox;
|
private ComboBox<ToneVolume> mDropToneVolumeComboBox;
|
||||||
|
|
||||||
@@ -114,18 +106,6 @@ public class PlaybackPreferenceEditor extends HBox
|
|||||||
mEditorPane.add(getStartToneVolumeComboBox(), 4, row);
|
mEditorPane.add(getStartToneVolumeComboBox(), 4, row);
|
||||||
mEditorPane.add(getTestStartToneButton(), 5, row);
|
mEditorPane.add(getTestStartToneButton(), 5, row);
|
||||||
|
|
||||||
mEditorPane.add(getUseAudioSegmentPreemptToneSwitch(), 0, ++row);
|
|
||||||
mEditorPane.add(new Label("Priority Preempt Tone"), 1, row, 3, 1);
|
|
||||||
Label preemptFrequencyLabel = new Label("Frequency:");
|
|
||||||
GridPane.setHalignment(preemptFrequencyLabel, HPos.RIGHT);
|
|
||||||
mEditorPane.add(preemptFrequencyLabel, 1, ++row);
|
|
||||||
mEditorPane.add(getPreemptToneFrequencyComboBox(), 2, row);
|
|
||||||
Label preemptVolumeLabel = new Label("Volume:");
|
|
||||||
GridPane.setHalignment(preemptVolumeLabel, HPos.RIGHT);
|
|
||||||
mEditorPane.add(preemptVolumeLabel, 3, row);
|
|
||||||
mEditorPane.add(getPreemptToneVolumeComboBox(), 4, row);
|
|
||||||
mEditorPane.add(getTestPreemptToneButton(), 5, row);
|
|
||||||
|
|
||||||
mEditorPane.add(getUseAudioSegmentDropToneSwitch(), 0, ++row);
|
mEditorPane.add(getUseAudioSegmentDropToneSwitch(), 0, ++row);
|
||||||
mEditorPane.add(new Label("Drop Tone - Do Not Monitor"), 1, row, 3, 1);
|
mEditorPane.add(new Label("Drop Tone - Do Not Monitor"), 1, row, 3, 1);
|
||||||
Label dropFrequencyLabel = new Label("Frequency:");
|
Label dropFrequencyLabel = new Label("Frequency:");
|
||||||
@@ -177,52 +157,21 @@ public class PlaybackPreferenceEditor extends HBox
|
|||||||
mUseAudioSegmentStartToneSwitch = new ToggleSwitch();
|
mUseAudioSegmentStartToneSwitch = new ToggleSwitch();
|
||||||
mUseAudioSegmentStartToneSwitch.setAlignment(Pos.BASELINE_RIGHT);
|
mUseAudioSegmentStartToneSwitch.setAlignment(Pos.BASELINE_RIGHT);
|
||||||
mUseAudioSegmentStartToneSwitch.setSelected(mPlaybackPreference.getUseAudioSegmentStartTone());
|
mUseAudioSegmentStartToneSwitch.setSelected(mPlaybackPreference.getUseAudioSegmentStartTone());
|
||||||
mUseAudioSegmentStartToneSwitch.selectedProperty().addListener(new ChangeListener<Boolean>()
|
mUseAudioSegmentStartToneSwitch.selectedProperty().addListener((observable, oldValue, newValue) ->
|
||||||
{
|
mPlaybackPreference.setUseAudioSegmentStartTone(newValue));
|
||||||
@Override
|
|
||||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
|
|
||||||
{
|
|
||||||
mPlaybackPreference.setUseAudioSegmentStartTone(newValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mUseAudioSegmentStartToneSwitch;
|
return mUseAudioSegmentStartToneSwitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ToggleSwitch getUseAudioSegmentPreemptToneSwitch()
|
|
||||||
{
|
|
||||||
if(mUseAudioSegmentPreemptToneSwitch == null)
|
|
||||||
{
|
|
||||||
mUseAudioSegmentPreemptToneSwitch = new ToggleSwitch();
|
|
||||||
mUseAudioSegmentPreemptToneSwitch.setSelected(mPlaybackPreference.getUseAudioSegmentPreemptTone());
|
|
||||||
mUseAudioSegmentPreemptToneSwitch.selectedProperty().addListener(new ChangeListener<Boolean>()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
|
|
||||||
{
|
|
||||||
mPlaybackPreference.setUseAudioSegmentPreemptTone(newValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return mUseAudioSegmentPreemptToneSwitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ToggleSwitch getUseAudioSegmentDropToneSwitch()
|
private ToggleSwitch getUseAudioSegmentDropToneSwitch()
|
||||||
{
|
{
|
||||||
if(mUseAudioSegmentDropToneSwitch == null)
|
if(mUseAudioSegmentDropToneSwitch == null)
|
||||||
{
|
{
|
||||||
mUseAudioSegmentDropToneSwitch = new ToggleSwitch();
|
mUseAudioSegmentDropToneSwitch = new ToggleSwitch();
|
||||||
mUseAudioSegmentDropToneSwitch.setSelected(mPlaybackPreference.getUseAudioSegmentDropTone());
|
mUseAudioSegmentDropToneSwitch.setSelected(mPlaybackPreference.getUseAudioSegmentDropTone());
|
||||||
mUseAudioSegmentDropToneSwitch.selectedProperty().addListener(new ChangeListener<Boolean>()
|
mUseAudioSegmentDropToneSwitch.selectedProperty().addListener((observable, oldValue, newValue) ->
|
||||||
{
|
mPlaybackPreference.setUseAudioSegmentDropTone(newValue));
|
||||||
@Override
|
|
||||||
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
|
|
||||||
{
|
|
||||||
mPlaybackPreference.setUseAudioSegmentDropTone(newValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mUseAudioSegmentDropToneSwitch;
|
return mUseAudioSegmentDropToneSwitch;
|
||||||
@@ -236,14 +185,7 @@ public class PlaybackPreferenceEditor extends HBox
|
|||||||
IconNode iconNode = new IconNode(FontAwesome.PLAY);
|
IconNode iconNode = new IconNode(FontAwesome.PLAY);
|
||||||
iconNode.setFill(Color.CORNFLOWERBLUE);
|
iconNode.setFill(Color.CORNFLOWERBLUE);
|
||||||
mTestStartToneButton.setGraphic(iconNode);
|
mTestStartToneButton.setGraphic(iconNode);
|
||||||
mTestStartToneButton.setOnAction(new EventHandler<ActionEvent>()
|
mTestStartToneButton.setOnAction(event -> play(mPlaybackPreference.getStartTone()));
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void handle(ActionEvent event)
|
|
||||||
{
|
|
||||||
play(mPlaybackPreference.getStartTone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mTestStartToneButton.disableProperty().bind(getUseAudioSegmentStartToneSwitch().selectedProperty().not());
|
mTestStartToneButton.disableProperty().bind(getUseAudioSegmentStartToneSwitch().selectedProperty().not());
|
||||||
}
|
}
|
||||||
@@ -251,29 +193,6 @@ public class PlaybackPreferenceEditor extends HBox
|
|||||||
return mTestStartToneButton;
|
return mTestStartToneButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Button getTestPreemptToneButton()
|
|
||||||
{
|
|
||||||
if(mTestPreemptToneButton == null)
|
|
||||||
{
|
|
||||||
mTestPreemptToneButton = new Button("Test");
|
|
||||||
IconNode iconNode = new IconNode(FontAwesome.PLAY);
|
|
||||||
iconNode.setFill(Color.CORNFLOWERBLUE);
|
|
||||||
mTestPreemptToneButton.setGraphic(iconNode);
|
|
||||||
mTestPreemptToneButton.setOnAction(new EventHandler<ActionEvent>()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void handle(ActionEvent event)
|
|
||||||
{
|
|
||||||
play(mPlaybackPreference.getPreemptTone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mTestPreemptToneButton.disableProperty().bind(getUseAudioSegmentPreemptToneSwitch().selectedProperty().not());
|
|
||||||
}
|
|
||||||
|
|
||||||
return mTestPreemptToneButton;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Button getTestDropToneButton()
|
public Button getTestDropToneButton()
|
||||||
{
|
{
|
||||||
if(mTestDropToneButton == null)
|
if(mTestDropToneButton == null)
|
||||||
@@ -282,14 +201,7 @@ public class PlaybackPreferenceEditor extends HBox
|
|||||||
IconNode iconNode = new IconNode(FontAwesome.PLAY);
|
IconNode iconNode = new IconNode(FontAwesome.PLAY);
|
||||||
iconNode.setFill(Color.CORNFLOWERBLUE);
|
iconNode.setFill(Color.CORNFLOWERBLUE);
|
||||||
mTestDropToneButton.setGraphic(iconNode);
|
mTestDropToneButton.setGraphic(iconNode);
|
||||||
mTestDropToneButton.setOnAction(new EventHandler<ActionEvent>()
|
mTestDropToneButton.setOnAction(event -> play(mPlaybackPreference.getDropTone()));
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void handle(ActionEvent event)
|
|
||||||
{
|
|
||||||
play(mPlaybackPreference.getDropTone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mTestDropToneButton.disableProperty().bind(getUseAudioSegmentDropToneSwitch().selectedProperty().not());
|
mTestDropToneButton.disableProperty().bind(getUseAudioSegmentDropToneSwitch().selectedProperty().not());
|
||||||
}
|
}
|
||||||
@@ -357,36 +269,6 @@ public class PlaybackPreferenceEditor extends HBox
|
|||||||
return mStartToneVolumeComboBox;
|
return mStartToneVolumeComboBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComboBox<ToneFrequency> getPreemptToneFrequencyComboBox()
|
|
||||||
{
|
|
||||||
if(mPreemptToneFrequencyComboBox == null)
|
|
||||||
{
|
|
||||||
mPreemptToneFrequencyComboBox = new ComboBox<>();
|
|
||||||
mPreemptToneFrequencyComboBox.getItems().addAll(ToneFrequency.values());
|
|
||||||
mPreemptToneFrequencyComboBox.getSelectionModel().select(mPlaybackPreference.getPreemptToneFrequency());
|
|
||||||
mPreemptToneFrequencyComboBox.getSelectionModel().selectedItemProperty()
|
|
||||||
.addListener((observable, oldValue, newValue) -> mPlaybackPreference.setPreemptToneFrequency(newValue));
|
|
||||||
mPreemptToneFrequencyComboBox.disableProperty().bind(getUseAudioSegmentPreemptToneSwitch().selectedProperty().not());
|
|
||||||
}
|
|
||||||
|
|
||||||
return mPreemptToneFrequencyComboBox;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ComboBox<ToneVolume> getPreemptToneVolumeComboBox()
|
|
||||||
{
|
|
||||||
if(mPreemptToneVolumeComboBox == null)
|
|
||||||
{
|
|
||||||
mPreemptToneVolumeComboBox = new ComboBox<>();
|
|
||||||
mPreemptToneVolumeComboBox.getItems().addAll(ToneVolume.values());
|
|
||||||
mPreemptToneVolumeComboBox.getSelectionModel().select(mPlaybackPreference.getPreemptToneVolume());
|
|
||||||
mPreemptToneVolumeComboBox.getSelectionModel().selectedItemProperty()
|
|
||||||
.addListener((observable, oldValue, newValue) -> mPlaybackPreference.setPreemptToneVolume(newValue));
|
|
||||||
mPreemptToneVolumeComboBox.disableProperty().bind(getUseAudioSegmentPreemptToneSwitch().selectedProperty().not());
|
|
||||||
}
|
|
||||||
|
|
||||||
return mPreemptToneVolumeComboBox;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays the audio buffer over the default mono playback device
|
* Plays the audio buffer over the default mono playback device
|
||||||
* @param audioSamples with 8 kHz mono PCM samples
|
* @param audioSamples with 8 kHz mono PCM samples
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.gui.preference.tuner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSPduo tuner select mode preferences.
|
||||||
|
*/
|
||||||
|
public enum RspDuoSelectionMode
|
||||||
|
{
|
||||||
|
DUAL("Dual Tuner"),
|
||||||
|
SINGLE_1("Single Tuner 1"),
|
||||||
|
SINGLE_2("Single Tuner 2");
|
||||||
|
|
||||||
|
private String mLabel;
|
||||||
|
|
||||||
|
RspDuoSelectionMode(String label)
|
||||||
|
{
|
||||||
|
mLabel = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup the enum entry from the value.
|
||||||
|
* @param value to match
|
||||||
|
* @return matched value or (default) DUAL if the value couldn't be matched.
|
||||||
|
*/
|
||||||
|
public static RspDuoSelectionMode fromValue(String value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return RspDuoSelectionMode.valueOf(value);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
//Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
return DUAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2020 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.gui.preference.tuner;
|
package io.github.dsheirer.gui.preference.tuner;
|
||||||
@@ -55,6 +52,8 @@ public class TunerPreferenceEditor extends HBox
|
|||||||
private Label mHelpTextPolyphaseLabel;
|
private Label mHelpTextPolyphaseLabel;
|
||||||
private Label mHeterodyneLabel;
|
private Label mHeterodyneLabel;
|
||||||
private Label mHelpTextHeterodyneLabel;
|
private Label mHelpTextHeterodyneLabel;
|
||||||
|
private ChoiceBox<RspDuoSelectionMode> mRspDuoTunerModeChoiceBox;
|
||||||
|
private Label mRspDuoModeLabel;
|
||||||
|
|
||||||
public TunerPreferenceEditor(UserPreferences userPreferences)
|
public TunerPreferenceEditor(UserPreferences userPreferences)
|
||||||
{
|
{
|
||||||
@@ -66,19 +65,24 @@ public class TunerPreferenceEditor extends HBox
|
|||||||
{
|
{
|
||||||
if(mEditorPane == null)
|
if(mEditorPane == null)
|
||||||
{
|
{
|
||||||
|
int row = 0;
|
||||||
mEditorPane = new GridPane();
|
mEditorPane = new GridPane();
|
||||||
mEditorPane.setVgap(10);
|
mEditorPane.setVgap(10);
|
||||||
mEditorPane.setHgap(10);
|
mEditorPane.setHgap(10);
|
||||||
mEditorPane.setPadding(new Insets(10, 10, 10, 10));
|
mEditorPane.setPadding(new Insets(10, 10, 10, 10));
|
||||||
GridPane.setHalignment(getChannelizerLabel(), HPos.LEFT);
|
GridPane.setHalignment(getChannelizerLabel(), HPos.RIGHT);
|
||||||
mEditorPane.add(getChannelizerLabel(), 0, 0);
|
mEditorPane.add(getChannelizerLabel(), 0, row);
|
||||||
mEditorPane.add(getChannelizerTypeChoiceBox(), 1, 0);
|
mEditorPane.add(getChannelizerTypeChoiceBox(), 1, row);
|
||||||
mEditorPane.add(new Separator(Orientation.HORIZONTAL), 0, 1, 2, 1);
|
mEditorPane.add(getPolyphaseLabel(), 0, ++row, 2, 1);
|
||||||
mEditorPane.add(getPolyphaseLabel(), 0, 2, 2, 1);
|
mEditorPane.add(getHelpTextPolyphaseLabel(), 0, ++row, 2, 3);
|
||||||
mEditorPane.add(getHelpTextPolyphaseLabel(), 0, 3, 2, 3);
|
row += 3;
|
||||||
mEditorPane.add(new Label(" "), 0, 6);
|
mEditorPane.add(new Label(" "), 0, row);
|
||||||
mEditorPane.add(getHeterodyneLabel(), 0, 7, 2, 1);
|
mEditorPane.add(getHeterodyneLabel(), 0, ++row, 2, 1);
|
||||||
mEditorPane.add(getHelpTextHeterodyneLabel(), 0, 8, 2, 3);
|
mEditorPane.add(getHelpTextHeterodyneLabel(), 0, ++row, 2, 3);
|
||||||
|
row += 3;
|
||||||
|
mEditorPane.add(new Separator(Orientation.HORIZONTAL), 0, row, 2, 1);
|
||||||
|
mEditorPane.add(getRspDuoModeLabel(), 0, ++row);
|
||||||
|
mEditorPane.add(getRspDuoTunerModeChoiceBox(), 1, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mEditorPane;
|
return mEditorPane;
|
||||||
@@ -161,4 +165,40 @@ public class TunerPreferenceEditor extends HBox
|
|||||||
|
|
||||||
return mHelpTextHeterodyneLabel;
|
return mHelpTextHeterodyneLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ChoiceBox<RspDuoSelectionMode> getRspDuoTunerModeChoiceBox()
|
||||||
|
{
|
||||||
|
if(mRspDuoTunerModeChoiceBox == null)
|
||||||
|
{
|
||||||
|
mRspDuoTunerModeChoiceBox = new ChoiceBox<>();
|
||||||
|
mRspDuoTunerModeChoiceBox.getItems().addAll(RspDuoSelectionMode.values());
|
||||||
|
|
||||||
|
RspDuoSelectionMode current = mTunerPreference.getRspDuoTunerMode();
|
||||||
|
mRspDuoTunerModeChoiceBox.getSelectionModel().select(current);
|
||||||
|
|
||||||
|
mRspDuoTunerModeChoiceBox.setOnAction(event -> {
|
||||||
|
RspDuoSelectionMode selected = mRspDuoTunerModeChoiceBox.getSelectionModel().getSelectedItem();
|
||||||
|
mTunerPreference.setRspDuoTunerMode(selected);
|
||||||
|
|
||||||
|
Label label = new Label("Please restart the application for this change to take effect");
|
||||||
|
label.setWrapText(true);
|
||||||
|
Alert alert = new Alert(Alert.AlertType.WARNING);
|
||||||
|
alert.getDialogPane().setContent(label);
|
||||||
|
alert.initOwner(((Node)getRspDuoTunerModeChoiceBox()).getScene().getWindow());
|
||||||
|
alert.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mRspDuoTunerModeChoiceBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Label getRspDuoModeLabel()
|
||||||
|
{
|
||||||
|
if(mRspDuoModeLabel == null)
|
||||||
|
{
|
||||||
|
mRspDuoModeLabel = new Label("SDRPlay RSPduo Selection Mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mRspDuoModeLabel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -53,6 +53,7 @@ public enum Form
|
|||||||
SITE,
|
SITE,
|
||||||
STATE,
|
STATE,
|
||||||
SYSTEM,
|
SYSTEM,
|
||||||
|
TALKER_ALIAS,
|
||||||
TALKGROUP,
|
TALKGROUP,
|
||||||
TELEPHONE_NUMBER,
|
TELEPHONE_NUMBER,
|
||||||
TONE,
|
TONE,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
package io.github.dsheirer.identifier;
|
package io.github.dsheirer.identifier;
|
||||||
|
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,6 +69,15 @@ public abstract class Identifier<T>
|
|||||||
return mValue;
|
return mValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow subclass implementations to set or update the value after construction.
|
||||||
|
* @param value to update or set.
|
||||||
|
*/
|
||||||
|
protected void setValue(T value)
|
||||||
|
{
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class/type of identifier
|
* Class/type of identifier
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General 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.identifier.alias;
|
||||||
|
|
||||||
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talker alias value provided by the network for the current talker (ie FROM).
|
||||||
|
*/
|
||||||
|
public class DmrTalkerAliasIdentifier extends TalkerAliasIdentifier
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
* @param value of the talker alias
|
||||||
|
*/
|
||||||
|
public DmrTalkerAliasIdentifier(String value)
|
||||||
|
{
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid()
|
||||||
|
{
|
||||||
|
return getValue() != null && !getValue().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Protocol getProtocol()
|
||||||
|
{
|
||||||
|
return Protocol.DMR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DmrTalkerAliasIdentifier create(String value)
|
||||||
|
{
|
||||||
|
return new DmrTalkerAliasIdentifier(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General 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.identifier.alias;
|
||||||
|
|
||||||
|
import io.github.dsheirer.identifier.Form;
|
||||||
|
import io.github.dsheirer.identifier.IdentifierClass;
|
||||||
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
import io.github.dsheirer.identifier.string.StringIdentifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Talker alias value provided by the network for the current talker (ie FROM).
|
||||||
|
*/
|
||||||
|
public abstract class TalkerAliasIdentifier extends StringIdentifier
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
* @param value of the string/alias identifier
|
||||||
|
*/
|
||||||
|
public TalkerAliasIdentifier(String value)
|
||||||
|
{
|
||||||
|
super(value, IdentifierClass.USER, Form.TALKER_ALIAS, Role.FROM);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid()
|
||||||
|
{
|
||||||
|
return getValue() != null && !getValue().isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -72,14 +72,15 @@ import io.github.dsheirer.source.SourceEvent;
|
|||||||
import io.github.dsheirer.source.heartbeat.Heartbeat;
|
import io.github.dsheirer.source.heartbeat.Heartbeat;
|
||||||
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
|
import io.github.dsheirer.source.heartbeat.IHeartbeatListener;
|
||||||
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider;
|
import io.github.dsheirer.source.heartbeat.IHeartbeatProvider;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processing chain provides a framework for connecting a complex or real sample
|
* Processing chain provides a framework for connecting a complex or real sample
|
||||||
@@ -116,12 +117,15 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
private Broadcaster<IMessage> mMessageBroadcaster = new Broadcaster<>();
|
private Broadcaster<IMessage> mMessageBroadcaster = new Broadcaster<>();
|
||||||
private Broadcaster<SquelchStateEvent> mSquelchStateEventBroadcaster = new Broadcaster<>();
|
private Broadcaster<SquelchStateEvent> mSquelchStateEventBroadcaster = new Broadcaster<>();
|
||||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||||
private List<Module> mModules = new ArrayList<>();
|
|
||||||
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(500);
|
private DecodeEventHistory mDecodeEventHistory = new DecodeEventHistory(500);
|
||||||
private MessageHistory mMessageHistory = new MessageHistory(500);
|
private MessageHistory mMessageHistory = new MessageHistory(500);
|
||||||
private AbstractChannelState mChannelState;
|
private AbstractChannelState mChannelState;
|
||||||
private EventBus mEventBus;
|
private EventBus mEventBus;
|
||||||
protected Source mSource;
|
protected Source mSource;
|
||||||
|
//Lock to protect access to the modules list.
|
||||||
|
private ReentrantLock mModuleLock = new ReentrantLock();
|
||||||
|
private List<Module> mModules = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a processing chain for managing a set of modules
|
* Creates a processing chain for managing a set of modules
|
||||||
@@ -196,30 +200,46 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
*/
|
*/
|
||||||
public void removeTrafficChannelManager()
|
public void removeTrafficChannelManager()
|
||||||
{
|
{
|
||||||
Iterator<Module> it = mModules.iterator();
|
mModuleLock.lock();
|
||||||
|
|
||||||
while(it.hasNext())
|
try
|
||||||
{
|
{
|
||||||
if(it.next() instanceof TrafficChannelManager)
|
Iterator<Module> it = mModules.iterator();
|
||||||
|
|
||||||
|
while(it.hasNext())
|
||||||
{
|
{
|
||||||
it.remove();
|
if(it.next() instanceof TrafficChannelManager)
|
||||||
|
{
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
stop();
|
stop();
|
||||||
|
|
||||||
List<Module> modules = new ArrayList<>(mModules);
|
mModuleLock.lock();
|
||||||
|
|
||||||
for(Module module : modules)
|
try
|
||||||
{
|
{
|
||||||
removeModule(module);
|
List<Module> modulesToRemove = new ArrayList<>(mModules);
|
||||||
module.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
mModules.clear();
|
for(Module module : modulesToRemove)
|
||||||
|
{
|
||||||
|
removeModule(module);
|
||||||
|
module.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
mAudioSegmentBroadcaster.dispose();
|
mAudioSegmentBroadcaster.dispose();
|
||||||
mDecodeEventBroadcaster.dispose();
|
mDecodeEventBroadcaster.dispose();
|
||||||
@@ -269,12 +289,21 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
addModule(mSource);
|
addModule(mSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source of sample data for this channel.
|
||||||
|
* @return source, may be null.
|
||||||
|
*/
|
||||||
|
public Source getSource()
|
||||||
|
{
|
||||||
|
return mSource;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of current modules for this processing chain
|
* List of current modules for this processing chain
|
||||||
*/
|
*/
|
||||||
public List<Module> getModules()
|
public List<Module> getModules()
|
||||||
{
|
{
|
||||||
return mModules;
|
return Collections.unmodifiableList(mModules);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -284,13 +313,22 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
{
|
{
|
||||||
List<DecoderState> decoderStates = new ArrayList<>();
|
List<DecoderState> decoderStates = new ArrayList<>();
|
||||||
|
|
||||||
for(Module module : mModules)
|
mModuleLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if(module instanceof DecoderState)
|
for(Module module : mModules)
|
||||||
{
|
{
|
||||||
decoderStates.add((DecoderState)module);
|
if(module instanceof DecoderState)
|
||||||
|
{
|
||||||
|
decoderStates.add((DecoderState)module);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
return decoderStates;
|
return decoderStates;
|
||||||
}
|
}
|
||||||
@@ -322,7 +360,17 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
*/
|
*/
|
||||||
public void addModule(Module module)
|
public void addModule(Module module)
|
||||||
{
|
{
|
||||||
mModules.add(module);
|
mModuleLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mModules.add(module);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
module.setInterModuleEventBus(getEventBus());
|
module.setInterModuleEventBus(getEventBus());
|
||||||
registerListeners(module);
|
registerListeners(module);
|
||||||
registerProviders(module);
|
registerProviders(module);
|
||||||
@@ -337,7 +385,17 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
unregisterListeners(module);
|
unregisterListeners(module);
|
||||||
unregisterProviders(module);
|
unregisterProviders(module);
|
||||||
module.setInterModuleEventBus(null);
|
module.setInterModuleEventBus(null);
|
||||||
mModules.remove(module);
|
|
||||||
|
mModuleLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mModules.remove(module);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -656,18 +714,27 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
+ "sample type - cannot start processing chain");
|
+ "sample type - cannot start processing chain");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Start each of the modules */
|
mModuleLock.lock();
|
||||||
for(Module module : mModules)
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
/* Start each of the modules */
|
||||||
|
for(Module module : mModules)
|
||||||
{
|
{
|
||||||
module.start();
|
try
|
||||||
}
|
{
|
||||||
catch(Exception e)
|
module.start();
|
||||||
{
|
}
|
||||||
mLog.error("Error starting module", e);
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error starting module", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -708,15 +775,28 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Stop each of the remaining modules */
|
/* Stop each of the remaining modules */
|
||||||
for(Module module : mModules)
|
mModuleLock.lock();
|
||||||
{
|
|
||||||
module.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Reset each of the modules
|
try
|
||||||
for(Module module : mModules)
|
|
||||||
{
|
{
|
||||||
module.reset();
|
for(Module module : mModules)
|
||||||
|
{
|
||||||
|
module.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Reset each of the modules
|
||||||
|
for(Module module : mModules)
|
||||||
|
{
|
||||||
|
module.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error stopping or resetting modules", e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -728,13 +808,22 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
{
|
{
|
||||||
List<Module> eventLoggingModules = new ArrayList<>();
|
List<Module> eventLoggingModules = new ArrayList<>();
|
||||||
|
|
||||||
for(Module module : mModules)
|
mModuleLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if(module instanceof EventLogger)
|
for(Module module : mModules)
|
||||||
{
|
{
|
||||||
eventLoggingModules.add(module);
|
if(module instanceof EventLogger)
|
||||||
|
{
|
||||||
|
eventLoggingModules.add(module);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
for(Module eventLoggingModule : eventLoggingModules)
|
for(Module eventLoggingModule : eventLoggingModules)
|
||||||
{
|
{
|
||||||
@@ -749,21 +838,30 @@ public class ProcessingChain implements Listener<ChannelEvent>
|
|||||||
{
|
{
|
||||||
List<Module> recordingModules = new ArrayList<>();
|
List<Module> recordingModules = new ArrayList<>();
|
||||||
|
|
||||||
for(Module module : mModules)
|
mModuleLock.lock();
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if(module instanceof ComplexSamplesWaveRecorder)
|
for(Module module : mModules)
|
||||||
{
|
{
|
||||||
recordingModules.add(module);
|
if(module instanceof ComplexSamplesWaveRecorder)
|
||||||
}
|
{
|
||||||
else if(module instanceof MBECallSequenceRecorder)
|
recordingModules.add(module);
|
||||||
{
|
}
|
||||||
recordingModules.add(module);
|
else if(module instanceof MBECallSequenceRecorder)
|
||||||
}
|
{
|
||||||
else if(module instanceof BinaryRecorder)
|
recordingModules.add(module);
|
||||||
{
|
}
|
||||||
recordingModules.add(module);
|
else if(module instanceof BinaryRecorder)
|
||||||
|
{
|
||||||
|
recordingModules.add(module);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
mModuleLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
for(Module recordingModule : recordingModules)
|
for(Module recordingModule : recordingModules)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -27,6 +27,7 @@ import io.github.dsheirer.channel.state.TimeslotDecoderState;
|
|||||||
import io.github.dsheirer.controller.channel.Channel;
|
import io.github.dsheirer.controller.channel.Channel;
|
||||||
import io.github.dsheirer.controller.channel.Channel.ChannelType;
|
import io.github.dsheirer.controller.channel.Channel.ChannelType;
|
||||||
import io.github.dsheirer.controller.channel.ChannelConfigurationChangeNotification;
|
import io.github.dsheirer.controller.channel.ChannelConfigurationChangeNotification;
|
||||||
|
import io.github.dsheirer.identifier.Form;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.IdentifierClass;
|
import io.github.dsheirer.identifier.IdentifierClass;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
@@ -59,6 +60,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.header.HeaderMessage;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.LCMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GPSInformation;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.GroupVoiceChannelUser;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasComplete;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.UnitToUnitVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraGroupVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.hytera.HyteraUnitToUnitVoiceChannelUser;
|
||||||
@@ -66,22 +68,24 @@ import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.Capaci
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.motorola.CapacityPlusWideAreaVoiceChannelUser;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.CapacityPlusRestChannel;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.packet.UDTShortMessageService;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
import io.github.dsheirer.module.decode.dmr.message.type.ServiceOptions;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceMessage;
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
import io.github.dsheirer.module.decode.event.DecodeEvent;
|
||||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||||
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
import io.github.dsheirer.module.decode.event.PlottableDecodeEvent;
|
||||||
|
import io.github.dsheirer.module.decode.ip.hytera.sds.HyteraUnknownPacket;
|
||||||
|
import io.github.dsheirer.module.decode.ip.hytera.sms.HyteraSmsPacket;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
import io.github.dsheirer.source.tuner.channel.rotation.AddChannelRotationActiveStateRequest;
|
||||||
import io.github.dsheirer.util.PacketUtil;
|
import io.github.dsheirer.util.PacketUtil;
|
||||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decoder state for an DMR channel. Maintains the call/data/idle state of the channel and produces events by
|
* Decoder state for an DMR channel. Maintains the call/data/idle state of the channel and produces events by
|
||||||
@@ -207,6 +211,10 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
processPacket((DMRPacketMessage)message);
|
processPacket((DMRPacketMessage)message);
|
||||||
}
|
}
|
||||||
|
else if(message instanceof UDTShortMessageService sms)
|
||||||
|
{
|
||||||
|
processSMS(sms);
|
||||||
|
}
|
||||||
else if(message instanceof DMRMessage)
|
else if(message instanceof DMRMessage)
|
||||||
{
|
{
|
||||||
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.CONTINUATION, State.ACTIVE, getTimeslot()));
|
||||||
@@ -269,6 +277,24 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a short data message carrying SMS text
|
||||||
|
* @param sms
|
||||||
|
*/
|
||||||
|
private void processSMS(UDTShortMessageService sms)
|
||||||
|
{
|
||||||
|
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
|
||||||
|
|
||||||
|
DecodeEvent smsEvent = DMRDecodeEvent.builder(sms.getTimestamp())
|
||||||
|
.eventType(DecodeEventType.SMS)
|
||||||
|
.eventDescription(DecodeEventType.SMS.toString())
|
||||||
|
.details("MESSAGE: " + sms.getSMS())
|
||||||
|
.identifiers(new IdentifierCollection(sms.getIdentifiers()))
|
||||||
|
.timeslot(sms.getTimeslot())
|
||||||
|
.build();
|
||||||
|
broadcast(smsEvent);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes a packet message
|
* Processes a packet message
|
||||||
*/
|
*/
|
||||||
@@ -276,27 +302,60 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
{
|
{
|
||||||
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
|
broadcast(new DecoderStateEvent(this, Event.START, State.DATA, getTimeslot()));
|
||||||
|
|
||||||
DecodeEvent packetEvent = DMRDecodeEvent.builder(packet.getTimestamp())
|
//Hytera SDS Long SMS message
|
||||||
.eventDescription(DecodeEventType.DATA_PACKET.name())
|
if(packet.getPacket() instanceof HyteraSmsPacket hyteraSmsPacket)
|
||||||
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
{
|
||||||
.timeslot(packet.getTimeslot())
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
.details(packet.toString())
|
mic.remove(Form.RADIO);
|
||||||
.build();
|
mic.remove(Form.TALKGROUP);
|
||||||
|
mic.update(hyteraSmsPacket.getSource());
|
||||||
|
mic.update(hyteraSmsPacket.getDestination());
|
||||||
|
|
||||||
broadcast(packetEvent);
|
DecodeEvent smsEvent = DMRDecodeEvent.builder(packet.getTimestamp())
|
||||||
|
.eventDescription(DecodeEventType.SMS.name())
|
||||||
|
.identifiers(mic)
|
||||||
|
.timeslot(packet.getTimeslot())
|
||||||
|
.details("SMS:" + hyteraSmsPacket.getSMS())
|
||||||
|
.build();
|
||||||
|
broadcast(smsEvent);
|
||||||
|
}
|
||||||
|
//Unknown Hytera Long Data Service Token Message
|
||||||
|
else if(packet.getPacket() instanceof HyteraUnknownPacket hyteraUnknownPacket)
|
||||||
|
{
|
||||||
|
MutableIdentifierCollection mic = new MutableIdentifierCollection(packet.getIdentifiers());
|
||||||
|
|
||||||
GeoPosition geoPosition = PacketUtil.extractGeoPosition(packet.getPacket());
|
DecodeEvent unknownTokenEvent = DMRDecodeEvent.builder(packet.getTimestamp())
|
||||||
|
.eventDescription(DecodeEventType.UNKNOWN_PACKET.name())
|
||||||
if (geoPosition != null) {
|
.identifiers(mic)
|
||||||
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(packet.getTimestamp())
|
.timeslot(packet.getTimeslot())
|
||||||
.channel(getCurrentChannel())
|
.details("HYTERA UNK TOKEN MSG:" + hyteraUnknownPacket.getHeader().toString())
|
||||||
.eventDescription(DecodeEventType.GPS.toString())
|
.build();
|
||||||
|
broadcast(unknownTokenEvent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DecodeEvent packetEvent = DMRDecodeEvent.builder(packet.getTimestamp())
|
||||||
|
.eventDescription(DecodeEventType.DATA_PACKET.name())
|
||||||
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
||||||
.protocol(Protocol.LRRP)
|
.timeslot(packet.getTimeslot())
|
||||||
.location(geoPosition)
|
.details(packet.toString())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
broadcast(plottableDecodeEvent);
|
broadcast(packetEvent);
|
||||||
|
|
||||||
|
GeoPosition geoPosition = PacketUtil.extractGeoPosition(packet.getPacket());
|
||||||
|
|
||||||
|
if (geoPosition != null) {
|
||||||
|
PlottableDecodeEvent plottableDecodeEvent = PlottableDecodeEvent.plottableBuilder(packet.getTimestamp())
|
||||||
|
.channel(getCurrentChannel())
|
||||||
|
.eventDescription(DecodeEventType.GPS.toString())
|
||||||
|
.identifiers(getMergedIdentifierCollection(packet.getIdentifiers()))
|
||||||
|
.protocol(Protocol.LRRP)
|
||||||
|
.location(geoPosition)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
broadcast(plottableDecodeEvent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,7 +664,7 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
break;
|
break;
|
||||||
case HYTERA_08_ANNOUNCEMENT:
|
case HYTERA_08_ANNOUNCEMENT:
|
||||||
case HYTERA_68_ANNOUNCEMENT:
|
case HYTERA_68_ANNOUNCEMENT:
|
||||||
case HYTERA_XPT_SITE_STATE:
|
case HYTERA_68_XPT_SITE_STATE:
|
||||||
|
|
||||||
case MOTOROLA_CAPPLUS_NEIGHBOR_REPORT:
|
case MOTOROLA_CAPPLUS_NEIGHBOR_REPORT:
|
||||||
if(csbk instanceof CapacityPlusNeighbors)
|
if(csbk instanceof CapacityPlusNeighbors)
|
||||||
@@ -1127,6 +1186,12 @@ public class DMRDecoderState extends TimeslotDecoderState
|
|||||||
broadcast(gpsEvent);
|
broadcast(gpsEvent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case FULL_STANDARD_TALKER_ALIAS_COMPLETE:
|
||||||
|
if(message instanceof TalkerAliasComplete tac && tac.hasTalkerAliasIdentifier())
|
||||||
|
{
|
||||||
|
getIdentifierCollection().update(tac.getTalkerAliasIdentifier());
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -30,17 +30,14 @@ import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
|
import io.github.dsheirer.module.decode.dmr.message.DMRMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.DMRMessageFactory;
|
import io.github.dsheirer.module.decode.dmr.message.DMRMessageFactory;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.packet.DMRPacketMessage;
|
||||||
import io.github.dsheirer.module.decode.ip.lrrp.LRRPPacket;
|
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.LRRPPacket;
|
||||||
import io.github.dsheirer.module.decode.ip.lrrp.token.Identity;
|
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.token.Identity;
|
||||||
import io.github.dsheirer.module.decode.ip.lrrp.token.Token;
|
import io.github.dsheirer.module.decode.ip.mototrbo.lrrp.token.Token;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import io.github.dsheirer.record.binary.BinaryReader;
|
import io.github.dsheirer.record.binary.BinaryReader;
|
||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.file.DirectoryStream;
|
import java.nio.file.DirectoryStream;
|
||||||
@@ -53,6 +50,8 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DMR Sync Detector and Message Framer. Includes capability to detect PLL out-of-phase lock errors
|
* DMR Sync Detector and Message Framer. Includes capability to detect PLL out-of-phase lock errors
|
||||||
|
|||||||
@@ -1,52 +1,56 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.module.decode.dmr;
|
package io.github.dsheirer.module.decode.dmr;
|
||||||
|
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.message.IMessage;
|
import io.github.dsheirer.message.IMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
import io.github.dsheirer.module.decode.dmr.channel.ITimeslotFrequencyReceiver;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
|
import io.github.dsheirer.module.decode.dmr.message.DMRBurst;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.IDLEMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
|
import io.github.dsheirer.module.decode.dmr.message.data.block.DataBlock;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Aloha;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.announcement.Announcement;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.MBCHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.PacketSequenceHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
import io.github.dsheirer.module.decode.dmr.message.data.header.ProprietaryDataHeader;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.header.UDTHeader;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FLCAssembler;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FLCAssembler;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.FullLCMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.full.TalkerAliasAssembler;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.SLCAssembler;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.SLCAssembler;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ShortLCMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.lc.shorty.ShortLCMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCAssembler;
|
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCAssembler;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBlock;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequenceAssembler;
|
import io.github.dsheirer.module.decode.dmr.message.data.packet.PacketSequenceAssembler;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.terminator.Terminator;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
|
import io.github.dsheirer.module.decode.dmr.message.voice.VoiceEMBMessage;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes DMR messages and performs re-assembly of link control fragments
|
* Processes DMR messages and performs re-assembly of link control fragments
|
||||||
@@ -54,12 +58,13 @@ import java.util.TreeMap;
|
|||||||
public class DMRMessageProcessor implements Listener<IMessage>
|
public class DMRMessageProcessor implements Listener<IMessage>
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
|
private final static Logger mLog = LoggerFactory.getLogger(DMRMessageProcessor.class);
|
||||||
|
private DecodeConfigDMR mConfigDMR;
|
||||||
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
|
private FLCAssembler mFLCAssemblerTimeslot1 = new FLCAssembler(1);
|
||||||
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
|
private FLCAssembler mFLCAssemblerTimeslot2 = new FLCAssembler(2);
|
||||||
private MBCAssembler mMBCAssembler = new MBCAssembler();
|
private MBCAssembler mMBCAssembler = new MBCAssembler();
|
||||||
private PacketSequenceAssembler mPacketSequenceAssembler;
|
private PacketSequenceAssembler mPacketSequenceAssembler;
|
||||||
private SLCAssembler mSLCAssembler = new SLCAssembler();
|
private SLCAssembler mSLCAssembler = new SLCAssembler();
|
||||||
|
private TalkerAliasAssembler mTalkerAliasAssembler = new TalkerAliasAssembler();
|
||||||
private Listener<IMessage> mMessageListener;
|
private Listener<IMessage> mMessageListener;
|
||||||
private Map<Integer,TimeslotFrequency> mTimeslotFrequencyMap = new TreeMap<>();
|
private Map<Integer,TimeslotFrequency> mTimeslotFrequencyMap = new TreeMap<>();
|
||||||
|
|
||||||
@@ -68,6 +73,8 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
|||||||
*/
|
*/
|
||||||
public DMRMessageProcessor(DecodeConfigDMR config)
|
public DMRMessageProcessor(DecodeConfigDMR config)
|
||||||
{
|
{
|
||||||
|
mConfigDMR = config;
|
||||||
|
|
||||||
for(TimeslotFrequency timeslotFrequency: config.getTimeslotMap())
|
for(TimeslotFrequency timeslotFrequency: config.getTimeslotMap())
|
||||||
{
|
{
|
||||||
mTimeslotFrequencyMap.put(timeslotFrequency.getNumber(), timeslotFrequency);
|
mTimeslotFrequencyMap.put(timeslotFrequency.getNumber(), timeslotFrequency);
|
||||||
@@ -157,22 +164,43 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Packet Sequence Message Assembly ...
|
//Packet Sequence Message Assembly ...
|
||||||
if(message instanceof Preamble)
|
if(message instanceof Preamble preamble)
|
||||||
{
|
{
|
||||||
mPacketSequenceAssembler.process((Preamble)message);
|
mPacketSequenceAssembler.process(preamble);
|
||||||
}
|
}
|
||||||
else if(message instanceof PacketSequenceHeader)
|
else if(message instanceof PacketSequenceHeader header)
|
||||||
{
|
{
|
||||||
mPacketSequenceAssembler.process((PacketSequenceHeader)message);
|
mPacketSequenceAssembler.process(header);
|
||||||
}
|
}
|
||||||
else if(message instanceof ProprietaryDataHeader)
|
else if(message instanceof ProprietaryDataHeader header)
|
||||||
{
|
{
|
||||||
mPacketSequenceAssembler.process(((ProprietaryDataHeader)message));
|
mPacketSequenceAssembler.process(header);
|
||||||
}
|
}
|
||||||
else if(message instanceof DataBlock)
|
else if(message instanceof DataBlock dataBlock)
|
||||||
{
|
{
|
||||||
mPacketSequenceAssembler.process((DataBlock)message);
|
mPacketSequenceAssembler.process(dataBlock);
|
||||||
}
|
}
|
||||||
|
else if(message instanceof UDTHeader header)
|
||||||
|
{
|
||||||
|
mPacketSequenceAssembler.process(header);
|
||||||
|
}
|
||||||
|
else if((message instanceof IDLEMessage || message instanceof Aloha || message instanceof Announcement) &&
|
||||||
|
message.getTimeslot() != 0)
|
||||||
|
{
|
||||||
|
mPacketSequenceAssembler.dispatchPacketSequence(message.getTimeslot());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Reset talker alias assembler on Idle or Terminator
|
||||||
|
if(message.isValid() && (message instanceof IDLEMessage || message instanceof Terminator))
|
||||||
|
{
|
||||||
|
mTalkerAliasAssembler.reset(message.getTimeslot());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Assemble Talker Alias from FLC message fragments (header & blocks 1-3)
|
||||||
|
if(message instanceof FullLCMessage flc && flc.getOpcode().isTalkerAliasOpcode() && message.isValid())
|
||||||
|
{
|
||||||
|
dispatch(mTalkerAliasAssembler.process(flc));
|
||||||
}
|
}
|
||||||
|
|
||||||
//Now that the message has been (potentially) enriched, dispatch it to the modules
|
//Now that the message has been (potentially) enriched, dispatch it to the modules
|
||||||
@@ -186,6 +214,26 @@ public class DMRMessageProcessor implements Listener<IMessage>
|
|||||||
{
|
{
|
||||||
if(mMessageListener != null && message != null)
|
if(mMessageListener != null && message != null)
|
||||||
{
|
{
|
||||||
|
//Configure compressed talkgroups for the DMR talkgroup identifiers in the message.
|
||||||
|
if(mConfigDMR.isUseCompressedTalkgroups())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for(Identifier identifier: message.getIdentifiers())
|
||||||
|
{
|
||||||
|
if(identifier instanceof DMRTalkgroup talkgroup)
|
||||||
|
{
|
||||||
|
talkgroup.setCompressed(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error applying compressed talkgroup setting to identifier in message: " + message +
|
||||||
|
" [" + message.getClass() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mMessageListener.receive(message);
|
mMessageListener.receive(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer, Zhenyu Mao
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.module.decode.dmr;
|
package io.github.dsheirer.module.decode.dmr;
|
||||||
|
|
||||||
@@ -25,7 +24,6 @@ import io.github.dsheirer.module.decode.DecoderType;
|
|||||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||||
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
import io.github.dsheirer.source.tuner.channel.ChannelSpecification;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -40,6 +38,7 @@ public class DecodeConfigDMR extends DecodeConfiguration
|
|||||||
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
|
private int mTrafficChannelPoolSize = TRAFFIC_CHANNEL_LIMIT_DEFAULT;
|
||||||
private boolean mIgnoreDataCalls = true;
|
private boolean mIgnoreDataCalls = true;
|
||||||
private boolean mIgnoreCRCChecksums = false;
|
private boolean mIgnoreCRCChecksums = false;
|
||||||
|
private boolean mUseCompressedTalkgroups = false;
|
||||||
private List<TimeslotFrequency> mTimeslotMap = new ArrayList<>();
|
private List<TimeslotFrequency> mTimeslotMap = new ArrayList<>();
|
||||||
|
|
||||||
public DecodeConfigDMR()
|
public DecodeConfigDMR()
|
||||||
@@ -109,6 +108,28 @@ public class DecodeConfigDMR extends DecodeConfiguration
|
|||||||
mIgnoreCRCChecksums = ignore;
|
mIgnoreCRCChecksums = ignore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if decoder should use compressed talkgroups
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "use_compressed_talkgroups")
|
||||||
|
public boolean isUseCompressedTalkgroups()
|
||||||
|
{
|
||||||
|
return mUseCompressedTalkgroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets flag to use compressed talkgroups.
|
||||||
|
*
|
||||||
|
* Converts the air interface value to a compressed value described in ETSI 102 361-2, V.2.4.1, Paragraph C2.1.2
|
||||||
|
* This has been observed in use on Hytera Tier III systems.
|
||||||
|
* See: https://cwh050.blogspot.com/2021/03/converting-flat-number-to-dmr-id.html?m=1
|
||||||
|
* @param useCompressedTalkgroups true to use compressed talkgroups.
|
||||||
|
*/
|
||||||
|
public void setUseCompressedTalkgroups(boolean useCompressedTalkgroups)
|
||||||
|
{
|
||||||
|
mUseCompressedTalkgroups = useCompressedTalkgroups;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Traffic channel pool size.
|
* Traffic channel pool size.
|
||||||
* @return
|
* @return
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.module.decode.dmr.identifier;
|
package io.github.dsheirer.module.decode.dmr.identifier;
|
||||||
@@ -26,16 +23,33 @@ import io.github.dsheirer.identifier.Role;
|
|||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DMR Talkgroup identifier
|
||||||
|
*/
|
||||||
public class DMRTalkgroup extends TalkgroupIdentifier
|
public class DMRTalkgroup extends TalkgroupIdentifier
|
||||||
{
|
{
|
||||||
public DMRTalkgroup(Integer value)
|
private boolean mCompressed = false;
|
||||||
|
private int mOriginalValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param value of the talkgroup
|
||||||
|
*/
|
||||||
|
public DMRTalkgroup(int value)
|
||||||
{
|
{
|
||||||
super(value, Role.TO);
|
super(value, Role.TO);
|
||||||
|
mOriginalValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DMRTalkgroup(Integer value, Role role)
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param value of the talkgroup
|
||||||
|
* @param role of the talkgroup
|
||||||
|
*/
|
||||||
|
public DMRTalkgroup(int value, Role role)
|
||||||
{
|
{
|
||||||
super(value, role);
|
super(value, role);
|
||||||
|
mOriginalValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -44,6 +58,84 @@ public class DMRTalkgroup extends TalkgroupIdentifier
|
|||||||
return Protocol.DMR;
|
return Protocol.DMR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
if(mCompressed)
|
||||||
|
{
|
||||||
|
return super.toString() + "(c)";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return super.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the compressed flag for this value.
|
||||||
|
*
|
||||||
|
* Talkgroup compression is an alternate format for DMR systems that allows the end user to dial a 7-digit number
|
||||||
|
* on their mobile keypad using just the digits 0 - 9, to address a talkgroup. The downside to this is that the
|
||||||
|
* total available talkgroup address space is reduced to just those dialable talkgroups.
|
||||||
|
*
|
||||||
|
* @param compressed true when using compressed talkgroup format.
|
||||||
|
*/
|
||||||
|
public void setCompressed(boolean compressed)
|
||||||
|
{
|
||||||
|
mCompressed = compressed;
|
||||||
|
|
||||||
|
if(mCompressed)
|
||||||
|
{
|
||||||
|
setValue(getCompressed(mOriginalValue));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setValue(mOriginalValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the air interface value to a compressed value described in ETSI 102 361-2, V.2.4.1, Paragraph C2.1.2
|
||||||
|
*
|
||||||
|
* This has been observed in use on Hytera Tier III systems.
|
||||||
|
*
|
||||||
|
* See: https://cwh050.blogspot.com/2021/03/converting-flat-number-to-dmr-id.html?m=1
|
||||||
|
*
|
||||||
|
* @param original value to convert
|
||||||
|
* @return converted value.
|
||||||
|
*/
|
||||||
|
public static int getCompressed(int original)
|
||||||
|
{
|
||||||
|
int converted = 0;
|
||||||
|
int units = original / 1_464_100;
|
||||||
|
converted += (units * 1_000_000);
|
||||||
|
original -= (units * 1_464_100);
|
||||||
|
|
||||||
|
units = original / 146_410;
|
||||||
|
converted += (units * 100_000);
|
||||||
|
original -= (units * 146_410);
|
||||||
|
|
||||||
|
units = original / 14_641;
|
||||||
|
converted += (units * 10_000);
|
||||||
|
original -= (units * 14_641);
|
||||||
|
|
||||||
|
units = original / 1_331;
|
||||||
|
converted += (units * 1_000);
|
||||||
|
original -= (units * 1_331);
|
||||||
|
|
||||||
|
units = original / 121;
|
||||||
|
converted += (units * 100);
|
||||||
|
original -= (units * 121);
|
||||||
|
|
||||||
|
units = original / 11;
|
||||||
|
converted += (units * 10);
|
||||||
|
original -= (units * 11);
|
||||||
|
|
||||||
|
converted += original;
|
||||||
|
|
||||||
|
return converted;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a DMR TO talkgroup identifier
|
* Creates a DMR TO talkgroup identifier
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.identifier;
|
||||||
|
|
||||||
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.Tier3Gateway;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DMR Tier-III (ie trunked) radio identifier with custom overrides to correctly label Gateways & Identities
|
||||||
|
*/
|
||||||
|
public class DmrTier3Radio extends DMRRadio
|
||||||
|
{
|
||||||
|
public DmrTier3Radio(Integer value, Role role)
|
||||||
|
{
|
||||||
|
super(value, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGateway()
|
||||||
|
{
|
||||||
|
return Tier3Gateway.isGateway(getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
if(isGateway())
|
||||||
|
{
|
||||||
|
return Tier3Gateway.fromValue(getValue()).getLabel();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return super.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a DMR TO radio identifier
|
||||||
|
*/
|
||||||
|
public static RadioIdentifier createTo(int radioId)
|
||||||
|
{
|
||||||
|
return new DmrTier3Radio(radioId, Role.TO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a DMR FROM radio identifier
|
||||||
|
*/
|
||||||
|
public static RadioIdentifier createFrom(int radioId)
|
||||||
|
{
|
||||||
|
return new DmrTier3Radio(radioId, Role.FROM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a DMR ANY radio identifier
|
||||||
|
*/
|
||||||
|
public static RadioIdentifier createAny(int radioId)
|
||||||
|
{
|
||||||
|
return new DmrTier3Radio(radioId, Role.ANY);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -25,7 +25,6 @@ import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.DataMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.DataMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -34,6 +33,8 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public abstract class DataBlock extends DataMessage
|
public abstract class DataBlock extends DataMessage
|
||||||
{
|
{
|
||||||
|
private static final int[] DATA_BLOCK_SERIAL_NUMBER = new int[]{0, 1, 2, 3, 4, 5, 6};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance.
|
* Constructs an instance.
|
||||||
*
|
*
|
||||||
@@ -63,7 +64,11 @@ public abstract class DataBlock extends DataMessage
|
|||||||
/**
|
/**
|
||||||
* Serial number for a confirmed data block
|
* Serial number for a confirmed data block
|
||||||
*/
|
*/
|
||||||
public abstract int getDataBlockSerialNumber();
|
public int getDataBlockSerialNumber()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(DATA_BLOCK_SERIAL_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -29,7 +29,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
|||||||
*/
|
*/
|
||||||
public class DataBlock1Rate extends DataBlock
|
public class DataBlock1Rate extends DataBlock
|
||||||
{
|
{
|
||||||
private static final int[] DATA_BLOCK_SERIAL_NUMBER = new int[]{0, 1, 2, 3, 4, 5, 6};
|
|
||||||
private static final int[] CRC = new int[]{7, 8, 9, 10, 11, 12, 13, 14, 15};
|
private static final int[] CRC = new int[]{7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||||
private static final int CONFIRMED_PAYLOAD_START = 16;
|
private static final int CONFIRMED_PAYLOAD_START = 16;
|
||||||
private static final int UNCONFIRMED_PAYLOAD_START = 0;
|
private static final int UNCONFIRMED_PAYLOAD_START = 0;
|
||||||
@@ -56,17 +55,16 @@ public class DataBlock1Rate extends DataBlock
|
|||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("RATE 1/1 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append(" [CRC ERROR]");
|
||||||
|
}
|
||||||
|
sb.append(" RATE 1/1 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
|
||||||
sb.append(" UNCONFIRMED:").append(getUnConfirmedPayload().toHexString());
|
sb.append(" UNCONFIRMED:").append(getUnConfirmedPayload().toHexString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDataBlockSerialNumber()
|
|
||||||
{
|
|
||||||
return getMessage().getInt(DATA_BLOCK_SERIAL_NUMBER);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CorrectedBinaryMessage getConfirmedPayload()
|
public CorrectedBinaryMessage getConfirmedPayload()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -29,7 +29,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
|||||||
*/
|
*/
|
||||||
public class DataBlock1_2Rate extends DataBlock
|
public class DataBlock1_2Rate extends DataBlock
|
||||||
{
|
{
|
||||||
private static final int[] DATA_BLOCK_SERIAL_NUMBER = new int[]{0, 1, 2, 3, 4, 5, 6};
|
|
||||||
private static final int[] CRC = new int[]{7, 8, 9, 10, 11, 12, 13, 14, 15};
|
private static final int[] CRC = new int[]{7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||||
private static final int CONFIRMED_PAYLOAD_START = 16;
|
private static final int CONFIRMED_PAYLOAD_START = 16;
|
||||||
private static final int UNCONFIRMED_PAYLOAD_START = 0;
|
private static final int UNCONFIRMED_PAYLOAD_START = 0;
|
||||||
@@ -56,17 +55,16 @@ public class DataBlock1_2Rate extends DataBlock
|
|||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("RATE 1/2 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append(" [CRC ERROR]");
|
||||||
|
}
|
||||||
|
sb.append(" RATE 1/2 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
|
||||||
sb.append(" UNCONFIRMED:").append(getUnConfirmedPayload().toHexString());
|
sb.append(" UNCONFIRMED:").append(getUnConfirmedPayload().toHexString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDataBlockSerialNumber()
|
|
||||||
{
|
|
||||||
return getMessage().getInt(DATA_BLOCK_SERIAL_NUMBER);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CorrectedBinaryMessage getConfirmedPayload()
|
public CorrectedBinaryMessage getConfirmedPayload()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -29,7 +29,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
|||||||
*/
|
*/
|
||||||
public class DataBlock3_4Rate extends DataBlock
|
public class DataBlock3_4Rate extends DataBlock
|
||||||
{
|
{
|
||||||
private static final int[] DATA_BLOCK_SERIAL_NUMBER = new int[]{0, 1, 2, 3, 4, 5, 6};
|
|
||||||
private static final int[] CRC = new int[]{7, 8, 9, 10, 11, 12, 13, 14, 15};
|
private static final int[] CRC = new int[]{7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||||
private static final int CONFIRMED_PAYLOAD_START = 16;
|
private static final int CONFIRMED_PAYLOAD_START = 16;
|
||||||
private static final int UNCONFIRMED_PAYLOAD_START = 0;
|
private static final int UNCONFIRMED_PAYLOAD_START = 0;
|
||||||
@@ -57,17 +56,16 @@ public class DataBlock3_4Rate extends DataBlock
|
|||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("RATE 3/4 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append(" [CRC ERROR]");
|
||||||
|
}
|
||||||
|
sb.append(" RATE 3/4 DATA CONFIRMED:").append(getConfirmedPayload().toHexString());
|
||||||
sb.append(" UNCONFIRMED:").append(getUnConfirmedPayload().toHexString());
|
sb.append(" UNCONFIRMED:").append(getUnConfirmedPayload().toHexString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDataBlockSerialNumber()
|
|
||||||
{
|
|
||||||
return getMessage().getInt(DATA_BLOCK_SERIAL_NUMBER);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CorrectedBinaryMessage getConfirmedPayload()
|
public CorrectedBinaryMessage getConfirmedPayload()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -23,8 +23,14 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|||||||
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.Hytera08Acknowledge;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.Hytera68Acknowledge;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAdjacentSiteInformation;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAdjacentSiteInformation;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAloha;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAnnouncement;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraAnnouncement;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraCsbko44;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraCsbko47;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraSmsAvailableNotification;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTPreamble;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTPreamble;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTSiteState;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera.HyteraXPTSiteState;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.motorola.CapacityMaxAloha;
|
||||||
@@ -49,6 +55,7 @@ import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Aloha;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Clear;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Clear;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.MoveTSCC;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.MoveTSCC;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Preamble;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.Protect;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.Acknowledge;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.Acknowledge;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.AcknowledgeStatus;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.AcknowledgeStatus;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.RegistrationAccepted;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.standard.acknowledge.RegistrationAccepted;
|
||||||
@@ -78,7 +85,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.mbc.MBCContinuationBloc
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.mbc.UnknownMultiCSBK;
|
import io.github.dsheirer.module.decode.dmr.message.data.mbc.UnknownMultiCSBK;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
|
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.ServiceKind;
|
import io.github.dsheirer.module.decode.dmr.message.type.ServiceKind;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -200,6 +206,9 @@ public class CSBKMessageFactory
|
|||||||
case STANDARD_PRIVATE_VOICE_CHANNEL_GRANT:
|
case STANDARD_PRIVATE_VOICE_CHANNEL_GRANT:
|
||||||
csbk = new PrivateVoiceChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
|
csbk = new PrivateVoiceChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
|
case STANDARD_PROTECT:
|
||||||
|
csbk = new Protect(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
break;
|
||||||
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM:
|
case STANDARD_TALKGROUP_DATA_CHANNEL_GRANT_SINGLE_ITEM:
|
||||||
csbk = new TalkgroupDataChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
|
csbk = new TalkgroupDataChannelGrant(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
@@ -213,6 +222,9 @@ public class CSBKMessageFactory
|
|||||||
csbk = new Preamble(pattern, message, cach, slotType, timestamp, timeslot);
|
csbk = new Preamble(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case HYTERA_68_ALOHA:
|
||||||
|
csbk = new HyteraAloha(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
break;
|
||||||
case HYTERA_08_ANNOUNCEMENT:
|
case HYTERA_08_ANNOUNCEMENT:
|
||||||
case HYTERA_68_ANNOUNCEMENT:
|
case HYTERA_68_ANNOUNCEMENT:
|
||||||
AnnouncementType announcementType = HyteraAnnouncement.getAnnouncementType(message);
|
AnnouncementType announcementType = HyteraAnnouncement.getAnnouncementType(message);
|
||||||
@@ -226,13 +238,27 @@ public class CSBKMessageFactory
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case HYTERA_XPT_PREAMBLE:
|
case HYTERA_68_XPT_PREAMBLE:
|
||||||
csbk = new HyteraXPTPreamble(pattern, message, cach, slotType, timestamp, timeslot);
|
csbk = new HyteraXPTPreamble(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
case HYTERA_XPT_SITE_STATE:
|
case HYTERA_68_XPT_SITE_STATE:
|
||||||
csbk = new HyteraXPTSiteState(pattern, message, cach, slotType, timestamp, timeslot);
|
csbk = new HyteraXPTSiteState(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
|
case HYTERA_08_ACKNOWLEDGE:
|
||||||
|
csbk = new Hytera08Acknowledge(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
break;
|
||||||
|
case HYTERA_08_CSBKO_44:
|
||||||
|
csbk = new HyteraCsbko44(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
break;
|
||||||
|
case HYTERA_08_CSBKO_47:
|
||||||
|
csbk = new HyteraCsbko47(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
break;
|
||||||
|
case HYTERA_68_ACKNOWLEDGE:
|
||||||
|
csbk = new Hytera68Acknowledge(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
break;
|
||||||
|
case HYTERA_68_CSBKO_62:
|
||||||
|
csbk = new HyteraSmsAvailableNotification(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
break;
|
||||||
case MOTOROLA_CAPMAX_ALOHA:
|
case MOTOROLA_CAPMAX_ALOHA:
|
||||||
csbk = new CapacityMaxAloha(pattern, message, cach, slotType, timestamp, timeslot);
|
csbk = new CapacityMaxAloha(pattern, message, cach, slotType, timestamp, timeslot);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
package io.github.dsheirer.module.decode.dmr.message.data.csbk;
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk;
|
||||||
|
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
|
import io.github.dsheirer.module.decode.dmr.message.type.Vendor;
|
||||||
|
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -50,8 +49,8 @@ public enum Opcode
|
|||||||
STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_PAYLOAD(Vendor.STANDARD, 35, "ACKNOWLEDGE RESPONSE INBOUND PAYLOAD"),
|
STANDARD_ACKNOWLEDGE_RESPONSE_INBOUND_PAYLOAD(Vendor.STANDARD, 35, "ACKNOWLEDGE RESPONSE INBOUND PAYLOAD"),
|
||||||
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_OUTBOUND_HEADER(Vendor.STANDARD, 36, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
|
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_OUTBOUND_HEADER(Vendor.STANDARD, 36, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
|
||||||
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_INBOUND_HEADER(Vendor.STANDARD, 37, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
|
STANDARD_UNIFIED_DATA_TRANSPORT_FOR_DGNA_INBOUND_HEADER(Vendor.STANDARD, 37, "UNIFIED DATA TRANSPORT OUTBOUND HEADER"),
|
||||||
STANDARD_ANNOUNCEMENT(Vendor.STANDARD, 40, "ANNOUNCEMENT"),
|
|
||||||
STANDARD_NEGATIVE_ACKNOWLEDGE_RESPONSE(Vendor.STANDARD, 38, "NEGATIVE ACKNOWLEDGE RESPONSE"),
|
STANDARD_NEGATIVE_ACKNOWLEDGE_RESPONSE(Vendor.STANDARD, 38, "NEGATIVE ACKNOWLEDGE RESPONSE"),
|
||||||
|
STANDARD_ANNOUNCEMENT(Vendor.STANDARD, 40, "ANNOUNCEMENT"),
|
||||||
STANDARD_MAINTENANCE(Vendor.STANDARD, 42, "MAINTENANCE"),
|
STANDARD_MAINTENANCE(Vendor.STANDARD, 42, "MAINTENANCE"),
|
||||||
STANDARD_CLEAR(Vendor.STANDARD, 46, "CLEAR"),
|
STANDARD_CLEAR(Vendor.STANDARD, 46, "CLEAR"),
|
||||||
STANDARD_PROTECT(Vendor.STANDARD, 47, "PROTECT"),
|
STANDARD_PROTECT(Vendor.STANDARD, 47, "PROTECT"),
|
||||||
@@ -90,12 +89,19 @@ public enum Opcode
|
|||||||
MOTOROLA_CAPPLUS_PREAMBLE(Vendor.MOTOROLA_CAPACITY_PLUS, 61, "PREAMBLE"),
|
MOTOROLA_CAPPLUS_PREAMBLE(Vendor.MOTOROLA_CAPACITY_PLUS, 61, "PREAMBLE"),
|
||||||
MOTOROLA_CAPPLUS_SYSTEM_STATUS(Vendor.MOTOROLA_CAPACITY_PLUS, 62, "SYSTEM STATUS"),
|
MOTOROLA_CAPPLUS_SYSTEM_STATUS(Vendor.MOTOROLA_CAPACITY_PLUS, 62, "SYSTEM STATUS"),
|
||||||
|
|
||||||
HYTERA_XPT_SITE_STATE(Vendor.HYTERA_68, 10, "HYTERA XPT SITE STATE"),
|
HYTERA_08_ACKNOWLEDGE(Vendor.HYTERA_8, 32, "HYTERA 08 ACKNOWLEDGE"),
|
||||||
HYTERA_08_ANNOUNCEMENT(Vendor.HYTERA_8, 40, "T-III 08 ANNOUNCEMENT"),
|
HYTERA_08_ANNOUNCEMENT(Vendor.HYTERA_8, 40, "HYTERA 08 ANNOUNCEMENT"),
|
||||||
HYTERA_68_ANNOUNCEMENT(Vendor.HYTERA_68, 40, "T-III 68 ANNOUNCEMENT"),
|
HYTERA_08_CSBKO_44(Vendor.HYTERA_8, 44, "HYTERA 08 CSBKO 44"),
|
||||||
|
HYTERA_08_CSBKO_47(Vendor.HYTERA_8, 47, "HYTERA 08 CSBKO 47"),
|
||||||
|
|
||||||
|
HYTERA_68_XPT_SITE_STATE(Vendor.HYTERA_68, 10, "HYTERA 68 XPT SITE STATE"),
|
||||||
|
HYTERA_68_ALOHA(Vendor.HYTERA_68, 25, "HYTERA 68 ALOHA"),
|
||||||
|
HYTERA_68_ACKNOWLEDGE(Vendor.HYTERA_68, 32, "HYTERA 68 ACKNOWLEDGE"),
|
||||||
|
HYTERA_68_ANNOUNCEMENT(Vendor.HYTERA_68, 40, "HYTERA 68 ANNOUNCEMENT"),
|
||||||
//Opcode 54 and/or 55 - See patent on Hytera call forwarding
|
//Opcode 54 and/or 55 - See patent on Hytera call forwarding
|
||||||
HYTERA_XPT_PREAMBLE(Vendor.HYTERA_68, 61, "HYTERA XPT PREAMBLE"),
|
HYTERA_68_XPT_PREAMBLE(Vendor.HYTERA_68, 61, "HYTERA 68 XPT PREAMBLE"),
|
||||||
|
HYTERA_68_CSBKO_62(Vendor.HYTERA_68, 62, "HYTERA 68 CSBKO 62"),
|
||||||
|
|
||||||
|
|
||||||
UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN");
|
UNKNOWN(Vendor.UNKNOWN, -1, "UNKNOWN");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.Reason;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hytera DMR Tier III - CSBKO 32 - Possibly Acknowledge Response
|
||||||
|
*
|
||||||
|
* Analysis: ... this was transmitted on the traffic channel while the mobile was at the end of a call, in the terminator
|
||||||
|
* sequence. Since this comes from the SMS gateway 16776906, I wonder if this is a traffic channel message waiting notification?
|
||||||
|
*/
|
||||||
|
public class Hytera08Acknowledge extends CSBKMessage
|
||||||
|
{
|
||||||
|
private static final int TARGET_GROUP_INDIVIDUAL_FLAG = 16;
|
||||||
|
protected static final int[] RESPONSE_INFO = new int[]{16, 17, 18, 19, 20, 21, 22};
|
||||||
|
private static final int[] ACKNOWLEDGE_TYPE = new int[]{23, 24};
|
||||||
|
//Note: acknowledge type is the first 2 bits of the reason code - intentional overlap
|
||||||
|
private static final int[] REASON_CODE = new int[]{23, 24, 25, 26, 27, 28, 29, 30};
|
||||||
|
private static final int RESERVED = 31;
|
||||||
|
private static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
|
||||||
|
47, 48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] ADDITIONAL_INFO_SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
|
||||||
|
67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
|
protected List<Identifier> mIdentifiers;
|
||||||
|
private IntegerIdentifier mTargetAddress;
|
||||||
|
private RadioIdentifier mSourceRadio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param syncPattern for the CSBK
|
||||||
|
* @param message bits
|
||||||
|
* @param cach for the DMR burst
|
||||||
|
* @param slotType for this message
|
||||||
|
* @param timestamp
|
||||||
|
* @param timeslot
|
||||||
|
*/
|
||||||
|
public Hytera08Acknowledge(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
sb.append(" HYTERA 08 ").append(getAcknowledgeType());
|
||||||
|
sb.append(" REASON:");
|
||||||
|
|
||||||
|
if(getReason() == Reason.UNKNOWN)
|
||||||
|
{
|
||||||
|
sb.append("UNKNOWN REASON CODE:").append(getMessage().getInt(REASON_CODE));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append(getReason());
|
||||||
|
}
|
||||||
|
sb.append(" FM:").append(getSourceRadio());
|
||||||
|
sb.append(" TO:").append(getTargetAddress());
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the target address is a talkgroup (true) or radio/gateway (false).
|
||||||
|
*/
|
||||||
|
public boolean isTargetAddressTalkgroup()
|
||||||
|
{
|
||||||
|
return getMessage().get(TARGET_GROUP_INDIVIDUAL_FLAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acknowledgement message type
|
||||||
|
*/
|
||||||
|
public AcknowledgeType getAcknowledgeType()
|
||||||
|
{
|
||||||
|
return AcknowledgeType.fromValue(getMessage().getInt(ACKNOWLEDGE_TYPE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reason code.
|
||||||
|
*
|
||||||
|
* Note: the reason entries are coupled with the acknowledgement type and can optionally indicate when the
|
||||||
|
* additional information field contains amplifying information.
|
||||||
|
*/
|
||||||
|
public Reason getReason()
|
||||||
|
{
|
||||||
|
return getReason(getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to determine the reason code for the specified message
|
||||||
|
* @param message containing a reason code field
|
||||||
|
* @return reason
|
||||||
|
*/
|
||||||
|
public static Reason getReason(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
return Reason.fromValue(message.getInt(REASON_CODE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntegerIdentifier getTargetAddress()
|
||||||
|
{
|
||||||
|
if(mTargetAddress == null)
|
||||||
|
{
|
||||||
|
if(isTargetAddressTalkgroup())
|
||||||
|
{
|
||||||
|
mTargetAddress = DMRTalkgroup.create(getMessage().getInt(TARGET_ADDRESS));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mTargetAddress = DmrTier3Radio.createTo(getMessage().getInt(TARGET_ADDRESS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTargetAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSourceRadio()
|
||||||
|
{
|
||||||
|
return true; //TODO: delineate valid reason codes here
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source radio identifier
|
||||||
|
*
|
||||||
|
* Note: the source radio field is dual-purpose and contains either a source radio address or
|
||||||
|
* additional information, depending on the reason code and/or acknowledgement type. Use the
|
||||||
|
* hasSourceRadio() method to first check if this message carries a source radio identifier.
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getSourceRadio()
|
||||||
|
{
|
||||||
|
if(mSourceRadio == null)
|
||||||
|
{
|
||||||
|
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(ADDITIONAL_INFO_SOURCE_ADDRESS));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSourceRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
mIdentifiers.add(getTargetAddress());
|
||||||
|
|
||||||
|
if(hasSourceRadio())
|
||||||
|
{
|
||||||
|
mIdentifiers.add(getSourceRadio());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.integer.IntegerIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DMRTalkgroup;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.AcknowledgeType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.Reason;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hytera FID68 - CSBKO 32 - Probable Acknowledge
|
||||||
|
*
|
||||||
|
* Analysis: ... this was transmitted on the Tier III control channel
|
||||||
|
*/
|
||||||
|
public class Hytera68Acknowledge extends CSBKMessage
|
||||||
|
{
|
||||||
|
private static final int TARGET_GROUP_INDIVIDUAL_FLAG = 16;
|
||||||
|
protected static final int[] RESPONSE_INFO = new int[]{16, 17, 18, 19, 20, 21, 22};
|
||||||
|
private static final int[] ACKNOWLEDGE_TYPE = new int[]{23, 24};
|
||||||
|
//Note: acknowledge type is the first 2 bits of the reason code - intentional overlap
|
||||||
|
private static final int[] REASON_CODE = new int[]{23, 24, 25, 26, 27, 28, 29, 30};
|
||||||
|
private static final int RESERVED = 31;
|
||||||
|
private static final int[] TARGET_ADDRESS = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
|
||||||
|
47, 48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] ADDITIONAL_INFO_SOURCE_ADDRESS = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66,
|
||||||
|
67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
|
protected List<Identifier> mIdentifiers;
|
||||||
|
private IntegerIdentifier mTargetAddress;
|
||||||
|
private RadioIdentifier mSourceRadio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param syncPattern for the CSBK
|
||||||
|
* @param message bits
|
||||||
|
* @param cach for the DMR burst
|
||||||
|
* @param slotType for this message
|
||||||
|
* @param timestamp
|
||||||
|
* @param timeslot
|
||||||
|
*/
|
||||||
|
public Hytera68Acknowledge(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
sb.append(" HYTERA 68 ").append(getAcknowledgeType());
|
||||||
|
sb.append(" REASON:");
|
||||||
|
|
||||||
|
if(getReason() == Reason.UNKNOWN)
|
||||||
|
{
|
||||||
|
sb.append("UNKNOWN REASON CODE:").append(getMessage().getInt(REASON_CODE));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append(getReason());
|
||||||
|
}
|
||||||
|
sb.append(" FM:").append(getSourceRadio());
|
||||||
|
sb.append(" TO:").append(getTargetAddress());
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the target address is a talkgroup (true) or radio/gateway (false).
|
||||||
|
*/
|
||||||
|
public boolean isTargetAddressTalkgroup()
|
||||||
|
{
|
||||||
|
return getMessage().get(TARGET_GROUP_INDIVIDUAL_FLAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acknowledgement message type
|
||||||
|
*/
|
||||||
|
public AcknowledgeType getAcknowledgeType()
|
||||||
|
{
|
||||||
|
return AcknowledgeType.fromValue(getMessage().getInt(ACKNOWLEDGE_TYPE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reason code.
|
||||||
|
*
|
||||||
|
* Note: the reason entries are coupled with the acknowledgement type and can optionally indicate when the
|
||||||
|
* additional information field contains amplifying information.
|
||||||
|
*/
|
||||||
|
public Reason getReason()
|
||||||
|
{
|
||||||
|
return getReason(getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to determine the reason code for the specified message
|
||||||
|
* @param message containing a reason code field
|
||||||
|
* @return reason
|
||||||
|
*/
|
||||||
|
public static Reason getReason(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
return Reason.fromValue(message.getInt(REASON_CODE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntegerIdentifier getTargetAddress()
|
||||||
|
{
|
||||||
|
if(mTargetAddress == null)
|
||||||
|
{
|
||||||
|
if(isTargetAddressTalkgroup())
|
||||||
|
{
|
||||||
|
mTargetAddress = DMRTalkgroup.create(getMessage().getInt(TARGET_ADDRESS));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mTargetAddress = DmrTier3Radio.createTo(getMessage().getInt(TARGET_ADDRESS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mTargetAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSourceRadio()
|
||||||
|
{
|
||||||
|
return true; //TODO: delineate valid reason codes here
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source radio identifier
|
||||||
|
*
|
||||||
|
* Note: the source radio field is dual-purpose and contains either a source radio address or
|
||||||
|
* additional information, depending on the reason code and/or acknowledgement type. Use the
|
||||||
|
* hasSourceRadio() method to first check if this message carries a source radio identifier.
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getSourceRadio()
|
||||||
|
{
|
||||||
|
if(mSourceRadio == null)
|
||||||
|
{
|
||||||
|
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(ADDITIONAL_INFO_SOURCE_ADDRESS));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSourceRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
mIdentifiers.add(getTargetAddress());
|
||||||
|
|
||||||
|
if(hasSourceRadio())
|
||||||
|
{
|
||||||
|
mIdentifiers.add(getSourceRadio());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.ServiceFunction;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.type.Version;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hytera DMR Tier III - Aloha Message
|
||||||
|
*/
|
||||||
|
public class HyteraAloha extends CSBKMessage
|
||||||
|
{
|
||||||
|
//TODO: not sure what bits 16 to 31 are used for ... awaiting documentation.
|
||||||
|
//TODO: bits 32 to 79 seem to align with the standard aloha.
|
||||||
|
|
||||||
|
private static final int[] RESERVED = new int[]{16, 17};
|
||||||
|
private static final int SITE_TIMESLOT_SYNCHRONIZATION = 18;
|
||||||
|
private static final int[] VERSION = new int[]{19, 20, 21};
|
||||||
|
private static final int TIMING_OFFSET = 22;
|
||||||
|
private static final int ACTIVE_NETWORK_CONNECTION_FLAG = 23;
|
||||||
|
private static final int[] MASK = new int[]{24, 25, 26, 27, 28};
|
||||||
|
private static final int[] SERVICE_FUNCTION = new int[]{29, 30};
|
||||||
|
private static final int[] N_RAND_WAIT = new int[]{31, 32, 33, 34};
|
||||||
|
private static final int REGISTRATION_REQUIRED_FLAG = 35;
|
||||||
|
private static final int[] BACKOFF = new int[]{36, 37, 38, 39};
|
||||||
|
private static final int SYSTEM_IDENTITY_CODE_OFFSET = 40;
|
||||||
|
private static final int[] RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
|
||||||
|
73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
|
private SystemIdentityCode mSystemIdentityCode;
|
||||||
|
private RadioIdentifier mRadioIdentifier;
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param syncPattern for the CSBK
|
||||||
|
* @param message bits
|
||||||
|
* @param cach for the DMR burst
|
||||||
|
* @param slotType for this message
|
||||||
|
* @param timestamp
|
||||||
|
* @param timeslot
|
||||||
|
*/
|
||||||
|
public HyteraAloha(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
sb.append(" HYTERA ALOHA");
|
||||||
|
|
||||||
|
if(hasRadioIdentifier())
|
||||||
|
{
|
||||||
|
sb.append(" TO:").append(getRadioIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" ").append(getSystemIdentityCode().getModel());
|
||||||
|
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
|
||||||
|
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
|
||||||
|
if(hasActiveNetworkConnection())
|
||||||
|
{
|
||||||
|
sb.append(" NET-CONNECTED");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.append(" NET-DISCONNECTED");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" SERVICES:").append(getServiceFunction());
|
||||||
|
|
||||||
|
sb.append(" ETSI VER:").append(getVersion());
|
||||||
|
sb.append(" MASK:").append(getMask());
|
||||||
|
|
||||||
|
if(getSystemIdentityCode().getPAR().isMultipleControlChannels())
|
||||||
|
{
|
||||||
|
sb.append(" ").append(getSystemIdentityCode().getPAR());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(" ").append(getMessage().toHexString());
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Services provided by the control channel.
|
||||||
|
*/
|
||||||
|
public ServiceFunction getServiceFunction()
|
||||||
|
{
|
||||||
|
return ServiceFunction.fromValue(getMessage().getInt(SERVICE_FUNCTION));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mobile subscriber ID masking value. See: 102 361-4 p6.1.3
|
||||||
|
*/
|
||||||
|
public int getMask()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasActiveNetworkConnection()
|
||||||
|
{
|
||||||
|
return getMessage().get(ACTIVE_NETWORK_CONNECTION_FLAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DMR Tier III ETSI 102 361-4 ICD Version Number supported by this system
|
||||||
|
*/
|
||||||
|
public Version getVersion()
|
||||||
|
{
|
||||||
|
return Version.fromValue(getMessage().getInt(VERSION));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acknowledged radio identifier
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getRadioIdentifier()
|
||||||
|
{
|
||||||
|
if(mRadioIdentifier == null)
|
||||||
|
{
|
||||||
|
mRadioIdentifier = DmrTier3Radio.createTo(getMessage().getInt(RADIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mRadioIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasRadioIdentifier()
|
||||||
|
{
|
||||||
|
return getMessage().getInt(RADIO) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System Identity Code structure
|
||||||
|
*/
|
||||||
|
public SystemIdentityCode getSystemIdentityCode()
|
||||||
|
{
|
||||||
|
if(mSystemIdentityCode == null)
|
||||||
|
{
|
||||||
|
mSystemIdentityCode = new SystemIdentityCode(getMessage(), SYSTEM_IDENTITY_CODE_OFFSET, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSystemIdentityCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
if(hasRadioIdentifier())
|
||||||
|
{
|
||||||
|
mIdentifiers.add(getRadioIdentifier());
|
||||||
|
}
|
||||||
|
mIdentifiers.add(getSystemIdentityCode().getNetwork());
|
||||||
|
mIdentifiers.add(getSystemIdentityCode().getSite());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* *****************************************************************************
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -28,7 +28,6 @@ import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
|||||||
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
|
import io.github.dsheirer.module.decode.dmr.message.type.AnnouncementType;
|
||||||
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
import io.github.dsheirer.module.decode.dmr.message.type.SystemIdentityCode;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -78,7 +77,7 @@ public class HyteraAnnouncement extends CSBKMessage
|
|||||||
}
|
}
|
||||||
|
|
||||||
sb.append("CC:").append(getSlotType().getColorCode());
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
sb.append(" HYTERA ").append(getAnnouncementType());
|
sb.append(" HYTERA ANNOUNCEMENT-TYPE:").append(getAnnouncementType());
|
||||||
sb.append(" ").append(getSystemIdentityCode().getModel());
|
sb.append(" ").append(getSystemIdentityCode().getModel());
|
||||||
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
|
sb.append(" NETWORK:").append(getSystemIdentityCode().getNetwork());
|
||||||
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
|
sb.append(" SITE:").append(getSystemIdentityCode().getSite());
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hytera DMR Tier III - CSBKO 44 -
|
||||||
|
*
|
||||||
|
* Analysis: ... this was transmitted on the Tier III traffic channel during call termination. This CSBK was
|
||||||
|
* interleaved with every other Terminator message, starting after the first 2x terminator messages. This continued
|
||||||
|
* through the final teardown which was 3x Clear messages.
|
||||||
|
*/
|
||||||
|
public class HyteraCsbko44 extends CSBKMessage
|
||||||
|
{
|
||||||
|
private static final int[] UNKNOWN_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param syncPattern for the CSBK
|
||||||
|
* @param message bits
|
||||||
|
* @param cach for the DMR burst
|
||||||
|
* @param slotType for this message
|
||||||
|
* @param timestamp
|
||||||
|
* @param timeslot
|
||||||
|
*/
|
||||||
|
public HyteraCsbko44(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
sb.append(" HYTERA UNKNOWN CSBKO=44");
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hytera DMR Tier III - CSBKO 47 -
|
||||||
|
*
|
||||||
|
* Analysis: ... this was transmitted on the traffic channel while the mobile was at the end of a call, in the terminator
|
||||||
|
* sequence. Since this comes from the SMS gateway 16776906, I wonder if this is a traffic channel message waiting notification?
|
||||||
|
*/
|
||||||
|
public class HyteraCsbko47 extends CSBKMessage
|
||||||
|
{
|
||||||
|
private static final int[] UNKNOWN_1 = new int[]{16, 17, 18, 19, 20, 21, 22, 23};
|
||||||
|
private static final int[] UNKNOWN_2 = new int[]{24, 25, 26, 27, 28, 29, 30, 31};
|
||||||
|
private static final int[] DESTINATION_RADIO = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
|
||||||
|
49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] SOURCE_RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
|
||||||
|
73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
private RadioIdentifier mDestinationRadio;
|
||||||
|
private RadioIdentifier mSourceRadio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param syncPattern for the CSBK
|
||||||
|
* @param message bits
|
||||||
|
* @param cach for the DMR burst
|
||||||
|
* @param slotType for this message
|
||||||
|
* @param timestamp
|
||||||
|
* @param timeslot
|
||||||
|
*/
|
||||||
|
public HyteraCsbko47(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach, SlotType slotType, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
sb.append(" HYTERA CSBKO=47 ######### UNKNOWN");
|
||||||
|
sb.append(" FM:").append(getSourceRadio());
|
||||||
|
sb.append(" TO:").append(getDestinationRadio());
|
||||||
|
sb.append(" UNK1:").append(getUnknown1());
|
||||||
|
sb.append(" UNK2:").append(getUnknown2());
|
||||||
|
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Addressed (ie TO) radio identifier
|
||||||
|
* @return radio identifier
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getDestinationRadio()
|
||||||
|
{
|
||||||
|
if(mDestinationRadio == null)
|
||||||
|
{
|
||||||
|
mDestinationRadio = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION_RADIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mDestinationRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnknown1()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(UNKNOWN_1, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUnknown2()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(UNKNOWN_2, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source radio. Should be 16,777,906 the Hytera SMS gateway.
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getSourceRadio()
|
||||||
|
{
|
||||||
|
if(mSourceRadio == null)
|
||||||
|
{
|
||||||
|
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSourceRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
mIdentifiers.add(getDestinationRadio());
|
||||||
|
mIdentifiers.add(getSourceRadio());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2023 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.module.decode.dmr.message.data.csbk.hytera;
|
||||||
|
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.DMRSyncPattern;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.identifier.DmrTier3Radio;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.CACH;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.SlotType;
|
||||||
|
import io.github.dsheirer.module.decode.dmr.message.data.csbk.CSBKMessage;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hytera DMR Tier III - CSBKO 62 -
|
||||||
|
*
|
||||||
|
* Analysis: this message was observed sent on timeslot 2 (not the Tier III control timeslot 1) from the SMS gateway (16776906)
|
||||||
|
* and gets sent to each of the radios involved in the SMS transfer (sender/receiver).
|
||||||
|
*/
|
||||||
|
public class HyteraSmsAvailableNotification extends CSBKMessage
|
||||||
|
{
|
||||||
|
private static final int[] UNKNOWN = new int[]{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
|
||||||
|
private static final int[] DESTINATION_RADIO = new int[]{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
|
||||||
|
47, 48, 49, 50, 51, 52, 53, 54, 55};
|
||||||
|
private static final int[] SOURCE_RADIO = new int[]{56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
|
||||||
|
72, 73, 74, 75, 76, 77, 78, 79};
|
||||||
|
|
||||||
|
private List<Identifier> mIdentifiers;
|
||||||
|
private RadioIdentifier mDestinationRadio;
|
||||||
|
private RadioIdentifier mSourceRadio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param syncPattern for the CSBK
|
||||||
|
* @param message bits
|
||||||
|
* @param cach for the DMR burst
|
||||||
|
* @param slotType for this message
|
||||||
|
* @param timestamp of the message
|
||||||
|
* @param timeslot of the message
|
||||||
|
*/
|
||||||
|
public HyteraSmsAvailableNotification(DMRSyncPattern syncPattern, CorrectedBinaryMessage message, CACH cach,
|
||||||
|
SlotType slotType, long timestamp, int timeslot)
|
||||||
|
{
|
||||||
|
super(syncPattern, message, cach, slotType, timestamp, timeslot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if(!isValid())
|
||||||
|
{
|
||||||
|
sb.append("[CRC-ERROR] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("CC:").append(getSlotType().getColorCode());
|
||||||
|
sb.append(" HYTERA SMS MESSAGE AVAILABLE FOR:").append(getDestinationRadio());
|
||||||
|
sb.append(" FM:").append(getSourceRadio());
|
||||||
|
sb.append(" UNK:").append(getUnknown());
|
||||||
|
sb.append(" MSG:").append(getMessage().toHexString());
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Addressed (ie TO) radio identifier
|
||||||
|
* @return radio identifier
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getDestinationRadio()
|
||||||
|
{
|
||||||
|
if(mDestinationRadio == null)
|
||||||
|
{
|
||||||
|
mDestinationRadio = DmrTier3Radio.createTo(getMessage().getInt(DESTINATION_RADIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mDestinationRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unknown octets.
|
||||||
|
* @return two unknown octets as hex string
|
||||||
|
*/
|
||||||
|
public String getUnknown()
|
||||||
|
{
|
||||||
|
return getMessage().getHex(UNKNOWN, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source radio. Should be 16,777,906 the Hytera SMS gateway.
|
||||||
|
*/
|
||||||
|
public RadioIdentifier getSourceRadio()
|
||||||
|
{
|
||||||
|
if(mSourceRadio == null)
|
||||||
|
{
|
||||||
|
mSourceRadio = DmrTier3Radio.createFrom(getMessage().getInt(SOURCE_RADIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSourceRadio;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identifier> getIdentifiers()
|
||||||
|
{
|
||||||
|
if(mIdentifiers == null)
|
||||||
|
{
|
||||||
|
mIdentifiers = new ArrayList<>();
|
||||||
|
mIdentifiers.add(getDestinationRadio());
|
||||||
|
mIdentifiers.add(getSourceRadio());
|
||||||
|
}
|
||||||
|
|
||||||
|
return mIdentifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user