mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 09:07:27 -05:00
Compare commits
101 Commits
v0.5.3
...
v0.6.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09e19e1486 | ||
|
|
0f5478925c | ||
|
|
09dce0b0d1 | ||
|
|
ad98b1cb5e | ||
|
|
8effd97749 | ||
|
|
9dbed72c32 | ||
|
|
b4dd3692b8 | ||
|
|
030cbb856c | ||
|
|
511101e9b7 | ||
|
|
60dd2b2df7 | ||
|
|
218dc94e02 | ||
|
|
2440f578b1 | ||
|
|
09c87237cb | ||
|
|
3d974ddd91 | ||
|
|
1489c970ba | ||
|
|
9e0afca3d1 | ||
|
|
99eda4013e | ||
|
|
a62f81f890 | ||
|
|
66f0731731 | ||
|
|
dc239801c6 | ||
|
|
d4ba0988bc | ||
|
|
ad0f953b2a | ||
|
|
e1bc9eabd6 | ||
|
|
d3dc1a8667 | ||
|
|
bf278120de | ||
|
|
2f52f6e009 | ||
|
|
02363c3113 | ||
|
|
cfaac0fcbc | ||
|
|
bee0405e00 | ||
|
|
50bdf0ee81 | ||
|
|
a27ddbe0d0 | ||
|
|
0d652b9153 | ||
|
|
9468fd189b | ||
|
|
215737ad9b | ||
|
|
123c25b142 | ||
|
|
af56198f0e | ||
|
|
3c73fdfd6e | ||
|
|
b391ebbb5f | ||
|
|
bad5a1f9f0 | ||
|
|
a7470b463f | ||
|
|
d6c706cb35 | ||
|
|
a0d6950c75 | ||
|
|
2495200b36 | ||
|
|
bfe7ae0b1b | ||
|
|
4912abbf07 | ||
|
|
2f2998d02e | ||
|
|
c96349fa1c | ||
|
|
f640576cc3 | ||
|
|
77d725a488 | ||
|
|
2c0305f928 | ||
|
|
21fd61d507 | ||
|
|
8fb56a5342 | ||
|
|
5d7b73f876 | ||
|
|
85b2a38058 | ||
|
|
c78517ee75 | ||
|
|
ae116d3dfe | ||
|
|
93252f9a40 | ||
|
|
3c03a4db2e | ||
|
|
d0fcd617aa | ||
|
|
6ca0decbd6 | ||
|
|
ddd9ca933e | ||
|
|
b2cc29569e | ||
|
|
411735772b | ||
|
|
2ffb2963af | ||
|
|
227c88c8a2 | ||
|
|
cbe9c79498 | ||
|
|
1b8c100b43 | ||
|
|
30c5fd64f2 | ||
|
|
3a6b486340 | ||
|
|
ded7335dbf | ||
|
|
005a4f8119 | ||
|
|
1ac49bfa7f | ||
|
|
c302c4d121 | ||
|
|
b0a524cf62 | ||
|
|
d246ffff30 | ||
|
|
adfc8b2d6f | ||
|
|
ad2ab08466 | ||
|
|
f3369410b2 | ||
|
|
bac38a5bd8 | ||
|
|
e5f5df74ad | ||
|
|
b3837f581a | ||
|
|
b2ab6f58e9 | ||
|
|
e04b59ba15 | ||
|
|
366eacd234 | ||
|
|
f792876c0a | ||
|
|
19f7a5171b | ||
|
|
5b6377cc11 | ||
|
|
9502ca3069 | ||
|
|
77629000b2 | ||
|
|
ca7916f992 | ||
|
|
9de83843a3 | ||
|
|
cbab7bb0c9 | ||
|
|
3df8d60eed | ||
|
|
b39dd1a19f | ||
|
|
0af5963d08 | ||
|
|
4f8b8e978b | ||
|
|
5654254cbb | ||
|
|
11f37e896a | ||
|
|
e8f563deeb | ||
|
|
f877354a11 | ||
|
|
df3d3b5ddc |
10
.github/workflows/gradle.yml
vendored
10
.github/workflows/gradle.yml
vendored
@@ -21,14 +21,14 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Set up JDK 19
|
- name: Set up JDK 20
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: '19'
|
java-version: '20'
|
||||||
distribution: 'liberica'
|
distribution: 'liberica'
|
||||||
java-package: 'jdk+fx'
|
java-package: 'jdk+fx'
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
uses: gradle/gradle-build-action@937999e9cc2425eddc7fd62d1053baf041147db7
|
uses: gradle/gradle-build-action@v2
|
||||||
with:
|
with:
|
||||||
arguments: clean build
|
arguments: clean build
|
||||||
|
|||||||
47
.github/workflows/nightly.yml
vendored
Normal file
47
.github/workflows/nightly.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Create nightly release for Linux, Mac and Windows using the Linux runner.
|
||||||
|
name: Nightly Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
# Don't run nightly build for pull requests - uncomment to test changes to this action within scope of the PR review
|
||||||
|
# pull_request:
|
||||||
|
# branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
nightly:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up JDK 20
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: '20'
|
||||||
|
distribution: 'liberica'
|
||||||
|
java-package: 'jdk+fx'
|
||||||
|
|
||||||
|
- name: Build Linux and Mac releases with Gradle
|
||||||
|
uses: gradle/gradle-build-action@v2
|
||||||
|
with:
|
||||||
|
arguments: runtimeZipOthers -PprojectVersion=nightly
|
||||||
|
|
||||||
|
- name: Build Windows release with Gradle
|
||||||
|
uses: gradle/gradle-build-action@v2
|
||||||
|
with:
|
||||||
|
arguments: runtimeZipWindows -PprojectVersion=nightly
|
||||||
|
|
||||||
|
- name: Create Build Info file
|
||||||
|
run: |
|
||||||
|
echo -e "ref: $GITHUB_REF \ncommit: $GITHUB_SHA\nbuild: $(date +"%Y-%m-%dT%H:%M:%SZ")" > build/image/build_info.txt
|
||||||
|
|
||||||
|
- name: Add build artifacts to nightly release
|
||||||
|
uses: pyTooling/Actions/releaser@main
|
||||||
|
with:
|
||||||
|
tag: nightly
|
||||||
|
rm: false
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
files: |
|
||||||
|
build/image/*.zip
|
||||||
|
build/image/build_info.txt
|
||||||
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_19_PREVIEW" project-jdk-name="liberica-19.0.1.fx" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_20" default="true" project-jdk-name="liberica-20" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -1,8 +1,10 @@
|
|||||||

|

|
||||||
|

|
||||||
|
|
||||||
# sdrtrunk
|
# sdrtrunk
|
||||||
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
||||||
|
|
||||||
|
* [Help/Wiki Home Page](https://github.com/DSheirer/sdrtrunk/wiki)
|
||||||
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/Getting-Started)
|
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/Getting-Started)
|
||||||
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/User-Manual)
|
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/User-Manual)
|
||||||
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
|
* [Download](https://github.com/DSheirer/sdrtrunk/releases)
|
||||||
@@ -18,6 +20,13 @@ All release versions of sdrtrunk are available from the [releases](https://githu
|
|||||||
* **(beta)** These versions are currently being tested for bugs and functionality prior to final release.
|
* **(beta)** These versions are currently being tested for bugs and functionality prior to final release.
|
||||||
* **(final)** These versions have been tested and are the current release version.
|
* **(final)** These versions have been tested and are the current release version.
|
||||||
|
|
||||||
|
## Download Nightly Software Build
|
||||||
|
The [nightly](https://github.com/DSheirer/sdrtrunk/releases/tag/nightly) release contains current builds of the software
|
||||||
|
for all supported operating systems. This version of the software may contain bugs and may not run correctly. However,
|
||||||
|
it let's you preview the most recent changes and fixes before the next software release. **Always backup your
|
||||||
|
playlist(s) before you use the nightly builds.** Note: the nightly release is updated each time code changes are
|
||||||
|
committed to the code base, so it's not really 'nightly' as much as it is 'current'.
|
||||||
|
|
||||||
## Minimum System Requirements
|
## Minimum System Requirements
|
||||||
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
|
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac/Linux (64-bit, 10.14 or higher)
|
||||||
* **CPU:** 4-core
|
* **CPU:** 4-core
|
||||||
|
|||||||
230
artifacts/sdrplay-api-headers/v3_08/sdrplay_api.h
Normal file
230
artifacts/sdrplay-api-headers/v3_08/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)
|
||||||
|
//#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.08)
|
||||||
|
|
||||||
|
// 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_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Open(void);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Close(void);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_ApiVersion(float *apiVer);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_LockDeviceApi(void);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_UnlockDeviceApi(void);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_GetDevices(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SelectDevice(sdrplay_api_DeviceT *device);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_ReleaseDevice(sdrplay_api_DeviceT *device);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER const char* sdrplay_api_GetErrorString(sdrplay_api_ErrT err);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrorInfoT* sdrplay_api_GetLastError(sdrplay_api_DeviceT *device);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_DisableHeartbeat(void); // Must be called before sdrplay_api_SelectDevice()
|
||||||
|
//
|
||||||
|
// // Device API function definitions
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_DebugEnable(HANDLE dev, sdrplay_api_DbgLvl_t enable);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_GetDeviceParams(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Init(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Uninit(HANDLE dev);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_Update(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoActiveTuner(HANDLE dev, sdrplay_api_TunerSelectT *currentTuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER sdrplay_api_ErrT sdrplay_api_SwapRspDuoDualTunerModeSampleRate(HANDLE dev, double *currentSampleRate, double newSampleRate);
|
||||||
|
// _SDRPLAY_DLL_QUALIFIER 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_08/sdrplay_api_callback.h
Normal file
78
artifacts/sdrplay-api-headers/v3_08/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_08/sdrplay_api_control.h
Normal file
55
artifacts/sdrplay-api-headers/v3_08/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_08/sdrplay_api_dev.h
Normal file
51
artifacts/sdrplay-api-headers/v3_08/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_08/sdrplay_api_rsp1a.h
Normal file
22
artifacts/sdrplay-api-headers/v3_08/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_08/sdrplay_api_rsp2.h
Normal file
35
artifacts/sdrplay-api-headers/v3_08/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_08/sdrplay_api_rspDuo.h
Normal file
40
artifacts/sdrplay-api-headers/v3_08/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_08/sdrplay_api_rspDx.h
Normal file
45
artifacts/sdrplay-api-headers/v3_08/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_08/sdrplay_api_rx_channel.h
Normal file
21
artifacts/sdrplay-api-headers/v3_08/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_08/sdrplay_api_tuner.h
Normal file
93
artifacts/sdrplay-api-headers/v3_08/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
|
||||||
19
build.gradle
19
build.gradle
@@ -53,12 +53,13 @@ repositories {
|
|||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
|
|
||||||
version = '0.6.0-alpha5'
|
//Version is defined in gradle.properties so that we can CLI override it for nightly builds.
|
||||||
|
version = project.findProperty('projectVersion')
|
||||||
|
|
||||||
//Java 19 is required for this version of the Project Panama preview/incubator feature
|
//Java 20 is required for this version of the Project Panama preview/incubator feature
|
||||||
java {
|
java {
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(19)
|
languageVersion = JavaLanguageVersion.of(20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,16 +198,16 @@ String targetWindowsX86_64 = 'windows-x86_64-v' + version
|
|||||||
* author's development environment, but can be changed to your environment if you want to use this build pattern.
|
* author's development environment, but can be changed to your environment if you want to use this build pattern.
|
||||||
*/
|
*/
|
||||||
String jdk_base = '/home/denny/java_jdks/'
|
String jdk_base = '/home/denny/java_jdks/'
|
||||||
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-19.0.1-full'
|
String jdk_linux_aarch64 = jdk_base + 'linux-arm64/jdk-20.0.1-full'
|
||||||
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-19.0.1-full'
|
String jdk_linux_x86_64 = jdk_base + 'linux-x64/jdk-20.0.1-full'
|
||||||
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-19.0.1-full.jdk'
|
String jdk_osx_x86_64 = jdk_base + 'osx-x64/jdk-20.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-20.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-20.0.1-full'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download URLs to download the JDK as part of the gradle build packaging process
|
* 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_base = "https://download.bell-sw.com/java/20.0.1+10/bellsoft-jdk20.0.1+10-"
|
||||||
def jdk_download_suffix = "-full.tar.gz"
|
def jdk_download_suffix = "-full.tar.gz"
|
||||||
def jdk_download_linux_aarch64 = jdk_download_base + "linux-aarch64" + jdk_download_suffix
|
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_linux_x86_64 = jdk_download_base + "linux-amd64" + jdk_download_suffix
|
||||||
|
|||||||
29
gradle.properties
Normal file
29
gradle.properties
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#
|
||||||
|
# ******************************************************************************
|
||||||
|
# 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/>
|
||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
|
||||||
|
# Version naming conventions:
|
||||||
|
# Alpha: maj.min.patch-alpha-build
|
||||||
|
# Beta: maj.min.patch-beta-build
|
||||||
|
# Release: maj.min.patch
|
||||||
|
#
|
||||||
|
# Note: this is so that the file names for the resultant release build products match the GitLab release asset
|
||||||
|
# tag name, specifically the dashes inserted before and after the alpha/beta labels.
|
||||||
|
# See: https://github.com/DSheirer/sdrtrunk/issues/1651
|
||||||
|
|
||||||
|
projectVersion=0.6.0-beta-4
|
||||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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,7 @@
|
|||||||
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -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,22 +19,15 @@
|
|||||||
package io.github.dsheirer.alias;
|
package io.github.dsheirer.alias;
|
||||||
|
|
||||||
import io.github.dsheirer.alias.action.AliasAction;
|
import io.github.dsheirer.alias.action.AliasAction;
|
||||||
import io.github.dsheirer.alias.action.AliasActionType;
|
|
||||||
import io.github.dsheirer.alias.action.beep.BeepAction;
|
import io.github.dsheirer.alias.action.beep.BeepAction;
|
||||||
import io.github.dsheirer.alias.action.clip.ClipAction;
|
import io.github.dsheirer.alias.action.clip.ClipAction;
|
||||||
import io.github.dsheirer.alias.action.script.ScriptAction;
|
import io.github.dsheirer.alias.action.script.ScriptAction;
|
||||||
import io.github.dsheirer.alias.id.AliasID;
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
import io.github.dsheirer.alias.id.AliasIDType;
|
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||||
|
import io.github.dsheirer.alias.id.dcs.Dcs;
|
||||||
import io.github.dsheirer.alias.id.esn.Esn;
|
import io.github.dsheirer.alias.id.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
|
import io.github.dsheirer.alias.id.legacy.mobileID.Min;
|
||||||
import io.github.dsheirer.alias.id.legacy.mpt1327.MPT1327ID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.nonrecordable.NonRecordable;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
|
import io.github.dsheirer.alias.id.legacy.siteID.SiteID;
|
||||||
import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID;
|
|
||||||
import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID;
|
|
||||||
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
|
||||||
import io.github.dsheirer.alias.id.priority.Priority;
|
import io.github.dsheirer.alias.id.priority.Priority;
|
||||||
import io.github.dsheirer.alias.id.radio.Radio;
|
import io.github.dsheirer.alias.id.radio.Radio;
|
||||||
@@ -45,11 +38,10 @@ import io.github.dsheirer.alias.id.status.UserStatusID;
|
|||||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||||
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 org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class AliasFactory
|
public class AliasFactory
|
||||||
{
|
{
|
||||||
@@ -64,6 +56,11 @@ public class AliasFactory
|
|||||||
BroadcastChannel copyBroadcast = new BroadcastChannel();
|
BroadcastChannel copyBroadcast = new BroadcastChannel();
|
||||||
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
|
copyBroadcast.setChannelName(originalBroadcast.getChannelName());
|
||||||
return copyBroadcast;
|
return copyBroadcast;
|
||||||
|
case DCS:
|
||||||
|
Dcs originalDcs = (Dcs)id;
|
||||||
|
Dcs copyDcs = new Dcs();
|
||||||
|
copyDcs.setDCSCode(originalDcs.getDCSCode());
|
||||||
|
return copyDcs;
|
||||||
case ESN:
|
case ESN:
|
||||||
Esn originalESN = (Esn)id;
|
Esn originalESN = (Esn)id;
|
||||||
Esn copyESN = new Esn();
|
Esn copyESN = new Esn();
|
||||||
@@ -234,65 +231,4 @@ public class AliasFactory
|
|||||||
|
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AliasID getAliasID(AliasIDType type)
|
|
||||||
{
|
|
||||||
switch(type)
|
|
||||||
{
|
|
||||||
case BROADCAST_CHANNEL:
|
|
||||||
return new BroadcastChannel();
|
|
||||||
case ESN:
|
|
||||||
return new Esn();
|
|
||||||
case FLEETSYNC:
|
|
||||||
return new FleetsyncID();
|
|
||||||
case LTR_NET_UID:
|
|
||||||
return new UniqueID();
|
|
||||||
case LOJACK:
|
|
||||||
return new LoJackFunctionAndID();
|
|
||||||
case MDC1200:
|
|
||||||
return new MDC1200ID();
|
|
||||||
case MIN:
|
|
||||||
return new Min();
|
|
||||||
case MPT1327:
|
|
||||||
return new MPT1327ID();
|
|
||||||
case NON_RECORDABLE:
|
|
||||||
return new NonRecordable();
|
|
||||||
case PRIORITY:
|
|
||||||
return new Priority();
|
|
||||||
case RADIO_ID:
|
|
||||||
return new Radio();
|
|
||||||
case RADIO_ID_RANGE:
|
|
||||||
return new RadioRange();
|
|
||||||
case RECORD:
|
|
||||||
return new Record();
|
|
||||||
case TALKGROUP:
|
|
||||||
return new Talkgroup();
|
|
||||||
case TALKGROUP_RANGE:
|
|
||||||
return new TalkgroupRange();
|
|
||||||
case SITE:
|
|
||||||
return new SiteID();
|
|
||||||
case STATUS:
|
|
||||||
return new UserStatusID();
|
|
||||||
case LEGACY_TALKGROUP:
|
|
||||||
return new LegacyTalkgroupID();
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unrecognized Alias ID type: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AliasAction getAliasAction(AliasActionType type)
|
|
||||||
{
|
|
||||||
switch(type)
|
|
||||||
{
|
|
||||||
case BEEP:
|
|
||||||
return new BeepAction();
|
|
||||||
case CLIP:
|
|
||||||
return new ClipAction();
|
|
||||||
case SCRIPT:
|
|
||||||
return new ScriptAction();
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unrecognized Alias Action type: " + type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -21,6 +21,7 @@ package io.github.dsheirer.alias;
|
|||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
import io.github.dsheirer.alias.id.AliasID;
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||||
|
import io.github.dsheirer.alias.id.dcs.Dcs;
|
||||||
import io.github.dsheirer.alias.id.esn.Esn;
|
import io.github.dsheirer.alias.id.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.priority.Priority;
|
import io.github.dsheirer.alias.id.priority.Priority;
|
||||||
import io.github.dsheirer.alias.id.radio.Radio;
|
import io.github.dsheirer.alias.id.radio.Radio;
|
||||||
@@ -32,6 +33,7 @@ import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
|||||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||||
import io.github.dsheirer.identifier.Identifier;
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
|
import io.github.dsheirer.identifier.dcs.DCSIdentifier;
|
||||||
import io.github.dsheirer.identifier.esn.ESNIdentifier;
|
import io.github.dsheirer.identifier.esn.ESNIdentifier;
|
||||||
import io.github.dsheirer.identifier.patch.PatchGroup;
|
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||||
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||||
@@ -41,12 +43,8 @@ import io.github.dsheirer.identifier.status.UserStatusIdentifier;
|
|||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.identifier.tone.ToneIdentifier;
|
import io.github.dsheirer.identifier.tone.ToneIdentifier;
|
||||||
import io.github.dsheirer.identifier.tone.ToneSequence;
|
import io.github.dsheirer.identifier.tone.ToneSequence;
|
||||||
|
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -57,6 +55,10 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
|
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
|
||||||
@@ -67,6 +69,7 @@ public class AliasList
|
|||||||
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
|
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
|
||||||
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
|
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
|
||||||
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
|
private Map<Protocol,RadioAliasList> mRadioProtocolMap = new EnumMap<>(Protocol.class);
|
||||||
|
private Map<DCSCode,Alias> mDCSCodeAliasMap = new EnumMap<>(DCSCode.class);
|
||||||
private Map<String,Alias> mESNMap = new HashMap<>();
|
private Map<String,Alias> mESNMap = new HashMap<>();
|
||||||
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
|
private Map<Integer,Alias> mUnitStatusMap = new HashMap<>();
|
||||||
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
|
private Map<Integer,Alias> mUserStatusMap = new HashMap<>();
|
||||||
@@ -178,6 +181,12 @@ public class AliasList
|
|||||||
|
|
||||||
radioRangeAliasList.add(radioRange, alias);
|
radioRangeAliasList.add(radioRange, alias);
|
||||||
break;
|
break;
|
||||||
|
case DCS:
|
||||||
|
if(id instanceof Dcs dcs)
|
||||||
|
{
|
||||||
|
mDCSCodeAliasMap.put(dcs.getDCSCode(), alias);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case ESN:
|
case ESN:
|
||||||
String esn = ((Esn)id).getEsn();
|
String esn = ((Esn)id).getEsn();
|
||||||
|
|
||||||
@@ -379,6 +388,8 @@ public class AliasList
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PATCH_GROUP:
|
case PATCH_GROUP:
|
||||||
|
List<Alias> aliases = new ArrayList<>();
|
||||||
|
|
||||||
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
|
PatchGroupIdentifier patchGroupIdentifier = (PatchGroupIdentifier)identifier;
|
||||||
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
||||||
|
|
||||||
@@ -386,8 +397,6 @@ public class AliasList
|
|||||||
|
|
||||||
if(patchGroupAliasList != null)
|
if(patchGroupAliasList != null)
|
||||||
{
|
{
|
||||||
List<Alias> aliases = new ArrayList<>();
|
|
||||||
|
|
||||||
Alias alias = patchGroupAliasList.getAlias(patchGroup.getPatchGroup());
|
Alias alias = patchGroupAliasList.getAlias(patchGroup.getPatchGroup());
|
||||||
|
|
||||||
if(alias != null)
|
if(alias != null)
|
||||||
@@ -395,19 +404,36 @@ public class AliasList
|
|||||||
aliases.add(alias);
|
aliases.add(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(TalkgroupIdentifier patchedGroup: patchGroup.getPatchedGroupIdentifiers())
|
for(TalkgroupIdentifier patchedTalkgroup: patchGroup.getPatchedTalkgroupIdentifiers())
|
||||||
{
|
{
|
||||||
Alias patchedAlias = patchGroupAliasList.getAlias(patchedGroup);
|
Alias patchedTalkgroupAlias = patchGroupAliasList.getAlias(patchedTalkgroup);
|
||||||
|
|
||||||
if(patchedAlias != null && !aliases.contains(patchedAlias))
|
if(patchedTalkgroupAlias != null && !aliases.contains(patchedTalkgroupAlias))
|
||||||
{
|
{
|
||||||
aliases.add(patchedAlias);
|
aliases.add(patchedTalkgroupAlias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return aliases;
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
if(patchGroup.hasPatchedRadios())
|
||||||
|
{
|
||||||
|
RadioAliasList radioAliasList = mRadioProtocolMap.get(patchGroupIdentifier.getProtocol());
|
||||||
|
|
||||||
|
if(radioAliasList != null)
|
||||||
|
{
|
||||||
|
for(RadioIdentifier patchedRadio: patchGroup.getPatchedRadioIdentifiers())
|
||||||
|
{
|
||||||
|
Alias patchedRadioAlias = radioAliasList.getAlias(patchedRadio);
|
||||||
|
|
||||||
|
if(patchedRadioAlias != null && !aliases.contains(patchedRadioAlias))
|
||||||
|
{
|
||||||
|
aliases.add(patchedRadioAlias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aliases;
|
||||||
case RADIO:
|
case RADIO:
|
||||||
RadioIdentifier radio = (RadioIdentifier)identifier;
|
RadioIdentifier radio = (RadioIdentifier)identifier;
|
||||||
|
|
||||||
@@ -418,7 +444,7 @@ public class AliasList
|
|||||||
return toList(radioAliasList.getAlias(radio));
|
return toList(radioAliasList.getAlias(radio));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ESN:
|
case DCS:
|
||||||
if(identifier instanceof ESNIdentifier)
|
if(identifier instanceof ESNIdentifier)
|
||||||
{
|
{
|
||||||
return toList(getESNAlias(((ESNIdentifier)identifier).getValue()));
|
return toList(getESNAlias(((ESNIdentifier)identifier).getValue()));
|
||||||
@@ -439,9 +465,9 @@ public class AliasList
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TONE:
|
case TONE:
|
||||||
if(identifier instanceof ToneIdentifier)
|
if(identifier instanceof ToneIdentifier toneIdentifier)
|
||||||
{
|
{
|
||||||
ToneSequence toneSequence = ((ToneIdentifier)identifier).getValue();
|
ToneSequence toneSequence = toneIdentifier.getValue();
|
||||||
|
|
||||||
if(toneSequence != null && toneSequence.hasTones())
|
if(toneSequence != null && toneSequence.hasTones())
|
||||||
{
|
{
|
||||||
@@ -454,6 +480,15 @@ public class AliasList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(identifier instanceof DCSIdentifier dcsIdentifier)
|
||||||
|
{
|
||||||
|
DCSCode dcsCode = dcsIdentifier.getValue();
|
||||||
|
|
||||||
|
if(dcsCode != null)
|
||||||
|
{
|
||||||
|
return toList(mDCSCodeAliasMap.get(dcsCode));
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,9 +500,7 @@ public class AliasList
|
|||||||
{
|
{
|
||||||
if(alias != null)
|
if(alias != null)
|
||||||
{
|
{
|
||||||
List<Alias> aliases = new ArrayList<>();
|
return Collections.singletonList(alias);
|
||||||
aliases.add(alias);
|
|
||||||
return aliases;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -23,17 +23,16 @@ import io.github.dsheirer.alias.id.AliasIDType;
|
|||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
|
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ListChangeListener;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alias Model contains all aliases and is responsible for creation and management of alias lists. Alias lists are a
|
* Alias Model contains all aliases and is responsible for creation and management of alias lists. Alias lists are a
|
||||||
@@ -184,10 +183,8 @@ public class AliasModel
|
|||||||
*/
|
*/
|
||||||
public void addAliases(List<Alias> aliases)
|
public void addAliases(List<Alias> aliases)
|
||||||
{
|
{
|
||||||
for(Alias alias : aliases)
|
removeAliases(aliases);
|
||||||
{
|
mAliases.addAll(aliases);
|
||||||
addAlias(alias);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -241,6 +238,26 @@ public class AliasModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the list of aliases from this model and any alias lists that might contain each alias.
|
||||||
|
* @param aliases
|
||||||
|
*/
|
||||||
|
public void removeAliases(List<Alias> aliases)
|
||||||
|
{
|
||||||
|
if(aliases != null && !aliases.isEmpty())
|
||||||
|
{
|
||||||
|
mAliases.removeAll(aliases);
|
||||||
|
|
||||||
|
for(Alias alias: aliases)
|
||||||
|
{
|
||||||
|
if(hasAliasList(alias.getAliasListName()))
|
||||||
|
{
|
||||||
|
getAliasList(alias.getAliasListName()).removeAlias(alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all aliases that match the alias list and have at least one alias ID of the specified type
|
* Retrieves all aliases that match the alias list and have at least one alias ID of the specified type
|
||||||
* @param aliasListName to search
|
* @param aliasListName to search
|
||||||
|
|||||||
@@ -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.alias.id;
|
package io.github.dsheirer.alias.id;
|
||||||
|
|
||||||
@@ -26,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
|
|||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||||
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
|
||||||
|
import io.github.dsheirer.alias.id.dcs.Dcs;
|
||||||
import io.github.dsheirer.alias.id.esn.Esn;
|
import io.github.dsheirer.alias.id.esn.Esn;
|
||||||
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
import io.github.dsheirer.alias.id.legacy.fleetsync.FleetsyncID;
|
||||||
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
import io.github.dsheirer.alias.id.legacy.mdc.MDC1200ID;
|
||||||
@@ -54,6 +52,7 @@ import javafx.util.Callback;
|
|||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||||
@JsonSubTypes({
|
@JsonSubTypes({
|
||||||
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
|
@JsonSubTypes.Type(value = BroadcastChannel.class, name = "broadcastChannel"),
|
||||||
|
@JsonSubTypes.Type(value = Dcs.class, name = "dcs"),
|
||||||
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
|
@JsonSubTypes.Type(value = Esn.class, name = "esn"),
|
||||||
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
|
@JsonSubTypes.Type(value = FleetsyncID.class, name = "fleetsyncID"),
|
||||||
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),
|
@JsonSubTypes.Type(value = LegacyTalkgroupID.class, name = "talkgroupID"),
|
||||||
|
|||||||
@@ -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.alias.id;
|
package io.github.dsheirer.alias.id;
|
||||||
|
|
||||||
@@ -26,6 +23,7 @@ import java.util.EnumSet;
|
|||||||
public enum AliasIDType
|
public enum AliasIDType
|
||||||
{
|
{
|
||||||
BROADCAST_CHANNEL("Audio Broadcast Channel"),
|
BROADCAST_CHANNEL("Audio Broadcast Channel"),
|
||||||
|
DCS("Digital Coded Squelch (DCS)"),
|
||||||
ESN("ESN"),
|
ESN("ESN"),
|
||||||
INVERT("Audio Inversion"),
|
INVERT("Audio Inversion"),
|
||||||
LOJACK("LoJack"),
|
LOJACK("LoJack"),
|
||||||
|
|||||||
111
src/main/java/io/github/dsheirer/alias/id/dcs/Dcs.java
Normal file
111
src/main/java/io/github/dsheirer/alias/id/dcs/Dcs.java
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.alias.id.dcs;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.alias.id.AliasID;
|
||||||
|
import io.github.dsheirer.alias.id.AliasIDType;
|
||||||
|
import io.github.dsheirer.module.decode.dcs.DCSCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Digital Coded Squelch (DCS) tone identifier
|
||||||
|
*/
|
||||||
|
public class Dcs extends AliasID implements Comparable<Dcs>
|
||||||
|
{
|
||||||
|
private DCSCode mDCSCode;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AliasIDType getType()
|
||||||
|
{
|
||||||
|
return AliasIDType.DCS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(AliasID id)
|
||||||
|
{
|
||||||
|
if(isValid() && id instanceof Dcs other)
|
||||||
|
{
|
||||||
|
return other.isValid() && getDCSCode().equals(other.getDCSCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DCS code
|
||||||
|
* @return DCS code or null.
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "code")
|
||||||
|
public DCSCode getDCSCode()
|
||||||
|
{
|
||||||
|
return mDCSCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the DCS code value.
|
||||||
|
* @param dcsCode to set
|
||||||
|
*/
|
||||||
|
public void setDCSCode(DCSCode dcsCode)
|
||||||
|
{
|
||||||
|
mDCSCode = dcsCode;
|
||||||
|
updateValueProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid()
|
||||||
|
{
|
||||||
|
return mDCSCode != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAudioIdentifier()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
if(isValid())
|
||||||
|
{
|
||||||
|
return getDCSCode().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "DCS-Invalid - No Tone Selected";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Dcs o)
|
||||||
|
{
|
||||||
|
if(isValid())
|
||||||
|
{
|
||||||
|
if(o.isValid())
|
||||||
|
{
|
||||||
|
return getDCSCode().compareTo(o.getDCSCode());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,6 +34,8 @@ import io.github.dsheirer.protocol.Protocol;
|
|||||||
*/
|
*/
|
||||||
public enum TalkgroupFormat
|
public enum TalkgroupFormat
|
||||||
{
|
{
|
||||||
|
AM("*****", 1, 0xFFFF, "1 to 65,535",
|
||||||
|
"AM valid value range is 1-65,535"),
|
||||||
APCO25("********", 0, 0xFFFF, "0 to 65,535",
|
APCO25("********", 0, 0xFFFF, "0 to 65,535",
|
||||||
"<html>APCO25 talkgroup valid range is 0 to 65,535"),
|
"<html>APCO25 talkgroup valid range is 0 to 65,535"),
|
||||||
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
|
DMR("********", 0, 0xFFFFFF, "0 to 16,777,215",
|
||||||
@@ -122,6 +124,8 @@ public enum TalkgroupFormat
|
|||||||
|
|
||||||
switch(protocol)
|
switch(protocol)
|
||||||
{
|
{
|
||||||
|
case AM:
|
||||||
|
return AM;
|
||||||
case APCO25:
|
case APCO25:
|
||||||
return APCO25;
|
return APCO25;
|
||||||
case FLEETSYNC:
|
case FLEETSYNC:
|
||||||
|
|||||||
@@ -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.alias.id.talkgroup;
|
package io.github.dsheirer.alias.id.talkgroup;
|
||||||
@@ -23,21 +22,20 @@ package io.github.dsheirer.alias.id.talkgroup;
|
|||||||
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
import io.github.dsheirer.preference.identifier.IntegerFormat;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.APCO25TalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.AbstractIntegerFormatter;
|
||||||
|
import io.github.dsheirer.preference.identifier.talkgroup.AnalogTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.DMRTalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.DMRTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.FleetsyncTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.LTRTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.MDC1200TalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.NBFMTalkgroupFormatter;
|
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.PassportTalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.PassportTalkgroupFormatter;
|
||||||
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
|
import io.github.dsheirer.preference.identifier.talkgroup.UnknownTalkgroupFormatter;
|
||||||
import io.github.dsheirer.protocol.Protocol;
|
import io.github.dsheirer.protocol.Protocol;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protocol-specific formatting of talkgroup values and parsing of formatted talkgroup values.
|
* Protocol-specific formatting of talkgroup values and parsing of formatted talkgroup values.
|
||||||
@@ -49,6 +47,10 @@ public class TalkgroupFormatter
|
|||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
|
AnalogTalkgroupFormatter analogTalkgroupFormatter = new AnalogTalkgroupFormatter();
|
||||||
|
mFormatterMap.put(Protocol.AM, analogTalkgroupFormatter);
|
||||||
|
mFormatterMap.put(Protocol.NBFM, analogTalkgroupFormatter);
|
||||||
|
|
||||||
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
|
mFormatterMap.put(Protocol.APCO25, new APCO25TalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
|
mFormatterMap.put(Protocol.DMR, new DMRTalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.FLEETSYNC, new FleetsyncTalkgroupFormatter());
|
mFormatterMap.put(Protocol.FLEETSYNC, new FleetsyncTalkgroupFormatter());
|
||||||
@@ -57,7 +59,6 @@ public class TalkgroupFormatter
|
|||||||
mFormatterMap.put(Protocol.LTR_NET, ltr);
|
mFormatterMap.put(Protocol.LTR_NET, ltr);
|
||||||
mFormatterMap.put(Protocol.MDC1200, new MDC1200TalkgroupFormatter());
|
mFormatterMap.put(Protocol.MDC1200, new MDC1200TalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.MPT1327, new MPT1327TalkgroupFormatter());
|
mFormatterMap.put(Protocol.MPT1327, new MPT1327TalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.NBFM, new NBFMTalkgroupFormatter());
|
|
||||||
mFormatterMap.put(Protocol.PASSPORT, new PassportTalkgroupFormatter());
|
mFormatterMap.put(Protocol.PASSPORT, new PassportTalkgroupFormatter());
|
||||||
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
|
mFormatterMap.put(Protocol.UNKNOWN, new UnknownTalkgroupFormatter());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -43,6 +43,7 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
|
private static final Logger mLog = LoggerFactory.getLogger(AudioModule.class);
|
||||||
private static float[] sHighPassFilterCoefficients;
|
private static float[] sHighPassFilterCoefficients;
|
||||||
|
private final boolean mAudioFilterEnable;
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
@@ -70,27 +71,33 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
|
private final IRealFilter mHighPassFilter = FilterFactory.getRealFilter(sHighPassFilterCoefficients);
|
||||||
private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
private final SquelchStateListener mSquelchStateListener = new SquelchStateListener();
|
||||||
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
private SquelchState mSquelchState = SquelchState.SQUELCH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Audio Module.
|
* Creates an Audio Module.
|
||||||
*
|
*
|
||||||
* @param aliasList for aliasing identifiers
|
* @param aliasList for aliasing identifiers
|
||||||
|
* @param timeslot for this audio module
|
||||||
* @param maxAudioSegmentLength in milliseconds
|
* @param maxAudioSegmentLength in milliseconds
|
||||||
|
* @param audioFilterEnable to enable or disable high-pass audio filter
|
||||||
*/
|
*/
|
||||||
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength)
|
public AudioModule(AliasList aliasList, int timeslot, long maxAudioSegmentLength, boolean audioFilterEnable)
|
||||||
{
|
{
|
||||||
super(aliasList, timeslot, maxAudioSegmentLength);
|
super(aliasList, timeslot, maxAudioSegmentLength);
|
||||||
|
mAudioFilterEnable = audioFilterEnable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Audio Module.
|
* Creates an Audio Module.
|
||||||
|
* @param aliasList for aliasing identifiers
|
||||||
|
* @param audioFilterEnable to enable or disable high-pass audio filter
|
||||||
*/
|
*/
|
||||||
public AudioModule(AliasList aliasList)
|
public AudioModule(AliasList aliasList, boolean audioFilterEnable)
|
||||||
{
|
{
|
||||||
super(aliasList);
|
super(aliasList);
|
||||||
|
mAudioFilterEnable = audioFilterEnable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -121,7 +128,11 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
{
|
{
|
||||||
if(mSquelchState == SquelchState.UNSQUELCH)
|
if(mSquelchState == SquelchState.UNSQUELCH)
|
||||||
{
|
{
|
||||||
audioBuffer = mHighPassFilter.filter(audioBuffer);
|
if(mAudioFilterEnable)
|
||||||
|
{
|
||||||
|
audioBuffer = mHighPassFilter.filter(audioBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
addAudio(audioBuffer);
|
addAudio(audioBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,11 +152,16 @@ public class AudioModule extends AbstractAudioModule implements ISquelchStateLis
|
|||||||
@Override
|
@Override
|
||||||
public void receive(SquelchStateEvent event)
|
public void receive(SquelchStateEvent event)
|
||||||
{
|
{
|
||||||
mSquelchState = event.getSquelchState();
|
SquelchState squelchState = event.getSquelchState();
|
||||||
|
|
||||||
if(mSquelchState == SquelchState.SQUELCH)
|
if(mSquelchState != squelchState)
|
||||||
{
|
{
|
||||||
closeAudioSegment();
|
mSquelchState = squelchState;
|
||||||
|
|
||||||
|
if(mSquelchState == SquelchState.SQUELCH)
|
||||||
|
{
|
||||||
|
closeAudioSegment();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
|||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
|
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
|
||||||
|
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
|
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
|
||||||
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
||||||
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
|
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
|
||||||
@@ -46,6 +47,7 @@ import java.net.SocketAddress;
|
|||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||||
@JsonSubTypes({
|
@JsonSubTypes({
|
||||||
@JsonSubTypes.Type(value = BroadcastifyCallConfiguration.class, name="broadcastifyCallConfiguration"),
|
@JsonSubTypes.Type(value = BroadcastifyCallConfiguration.class, name="broadcastifyCallConfiguration"),
|
||||||
|
@JsonSubTypes.Type(value = RdioScannerConfiguration.class, name="RdioScannerConfiguration"),
|
||||||
@JsonSubTypes.Type(value = IcecastConfiguration.class, name="icecastConfiguration"),
|
@JsonSubTypes.Type(value = IcecastConfiguration.class, name="icecastConfiguration"),
|
||||||
@JsonSubTypes.Type(value = ShoutcastV1Configuration.class, name="shoutcastV1Configuration"),
|
@JsonSubTypes.Type(value = ShoutcastV1Configuration.class, name="shoutcastV1Configuration"),
|
||||||
@JsonSubTypes.Type(value = ShoutcastV2Configuration.class, name="shoutcastV2Configuration"),
|
@JsonSubTypes.Type(value = ShoutcastV2Configuration.class, name="shoutcastV2Configuration"),
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ import io.github.dsheirer.alias.AliasModel;
|
|||||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallBroadcaster;
|
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallBroadcaster;
|
||||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
|
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyCallConfiguration;
|
||||||
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration;
|
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyFeedConfiguration;
|
||||||
|
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerBroadcaster;
|
||||||
|
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerConfiguration;
|
||||||
|
import io.github.dsheirer.audio.broadcast.rdioscanner.RdioScannerFeedConfiguration;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPAudioBroadcaster;
|
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPAudioBroadcaster;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPConfiguration;
|
import io.github.dsheirer.audio.broadcast.icecast.IcecastHTTPConfiguration;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPAudioBroadcaster;
|
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPAudioBroadcaster;
|
||||||
@@ -61,6 +64,9 @@ public class BroadcastFactory
|
|||||||
case BROADCASTIFY_CALL:
|
case BROADCASTIFY_CALL:
|
||||||
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration,
|
return new BroadcastifyCallBroadcaster((BroadcastifyCallConfiguration)configuration,
|
||||||
inputAudioFormat, mp3Setting, aliasModel);
|
inputAudioFormat, mp3Setting, aliasModel);
|
||||||
|
case RDIOSCANNER_CALL:
|
||||||
|
return new RdioScannerBroadcaster((RdioScannerConfiguration)configuration,
|
||||||
|
inputAudioFormat, mp3Setting, aliasModel);
|
||||||
case BROADCASTIFY:
|
case BROADCASTIFY:
|
||||||
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
|
return new IcecastTCPAudioBroadcaster((BroadcastifyFeedConfiguration) configuration,
|
||||||
inputAudioFormat, mp3Setting, aliasModel);
|
inputAudioFormat, mp3Setting, aliasModel);
|
||||||
@@ -99,6 +105,8 @@ public class BroadcastFactory
|
|||||||
{
|
{
|
||||||
case BROADCASTIFY_CALL:
|
case BROADCASTIFY_CALL:
|
||||||
return new BroadcastifyCallConfiguration(format);
|
return new BroadcastifyCallConfiguration(format);
|
||||||
|
case RDIOSCANNER_CALL:
|
||||||
|
return new RdioScannerConfiguration(format);
|
||||||
case BROADCASTIFY:
|
case BROADCASTIFY:
|
||||||
return new BroadcastifyFeedConfiguration(format);
|
return new BroadcastifyFeedConfiguration(format);
|
||||||
case ICECAST_HTTP:
|
case ICECAST_HTTP:
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public enum BroadcastServerType
|
|||||||
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
|
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
|
||||||
|
|
||||||
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
|
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
|
||||||
|
RDIOSCANNER_CALL("Rdio Scanner", "images/rdioscanner.png"),
|
||||||
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
|
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
|
||||||
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
|
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
|
||||||
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
|
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
|
||||||
|
|||||||
@@ -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,9 +38,6 @@ import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
|||||||
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -48,12 +45,13 @@ import java.net.http.HttpClient;
|
|||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audio broadcaster to push completed audio recordings to the Broadcastify call push API.
|
* Audio broadcaster to push completed audio recordings to the Broadcastify call push API.
|
||||||
@@ -475,7 +473,11 @@ public class BroadcastifyCallBroadcaster extends AbstractAudioBroadcaster<Broadc
|
|||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||||
for(TalkgroupIdentifier patched: patchGroup.getPatchedGroupIdentifiers())
|
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
|
||||||
|
{
|
||||||
|
sb.append(",").append(patched.getValue());
|
||||||
|
}
|
||||||
|
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
|
||||||
{
|
{
|
||||||
sb.append(",").append(patched.getValue());
|
sb.append(",").append(patched.getValue());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.broadcast.rdioscanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP headers used for posting to Rdio Scanner API
|
||||||
|
*/
|
||||||
|
public enum FormField
|
||||||
|
{
|
||||||
|
AUDIO("audio"),
|
||||||
|
AUDIO_NAME("audioName"),
|
||||||
|
AUDIO_TYPE("audioType"),
|
||||||
|
DATE_TIME("dateTime"),
|
||||||
|
FREQUENCIES("frequencies"),
|
||||||
|
FREQUENCY("frequency"),
|
||||||
|
KEY("key"),
|
||||||
|
PATCHES("patches"),
|
||||||
|
SOURCE("source"),
|
||||||
|
SOURCES("sources"),
|
||||||
|
SYSTEM("system"),
|
||||||
|
SYSTEM_LABEL("systemLabel"),
|
||||||
|
TALKGROUP_ID("talkgroup"),
|
||||||
|
TALKGROUP_GROUP("talkgroupGroup"),
|
||||||
|
TALKGROUP_LABEL("talkgroupLabel"),
|
||||||
|
TALKGROUP_TAG("talkgroupTag"),
|
||||||
|
TEST("test");
|
||||||
|
|
||||||
|
private String mHeader;
|
||||||
|
|
||||||
|
FormField(String header)
|
||||||
|
{
|
||||||
|
mHeader = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHeader()
|
||||||
|
{
|
||||||
|
return mHeader;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,619 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.audio.broadcast.rdioscanner;
|
||||||
|
|
||||||
|
import com.google.common.net.HttpHeaders;
|
||||||
|
import io.github.dsheirer.alias.Alias;
|
||||||
|
import io.github.dsheirer.alias.AliasList;
|
||||||
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
|
import io.github.dsheirer.audio.broadcast.AbstractAudioBroadcaster;
|
||||||
|
import io.github.dsheirer.audio.broadcast.AudioRecording;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastEvent;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||||
|
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||||
|
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||||
|
import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceDecoder;
|
||||||
|
import io.github.dsheirer.identifier.Form;
|
||||||
|
import io.github.dsheirer.identifier.Identifier;
|
||||||
|
import io.github.dsheirer.identifier.IdentifierClass;
|
||||||
|
import io.github.dsheirer.identifier.Role;
|
||||||
|
import io.github.dsheirer.identifier.configuration.ConfigurationLongIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.patch.PatchGroup;
|
||||||
|
import io.github.dsheirer.identifier.patch.PatchGroupIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.radio.RadioIdentifier;
|
||||||
|
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
|
||||||
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio broadcaster to push completed audio recordings to the Rdio Scanner call upload API.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class RdioScannerBroadcaster extends AbstractAudioBroadcaster<RdioScannerConfiguration>
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(RdioScannerBroadcaster.class);
|
||||||
|
|
||||||
|
private static final String ENCODING_TYPE_MP3 = "mp3";
|
||||||
|
private static final String MULTIPART_TYPE = "multipart";
|
||||||
|
private static final String DEFAULT_SUBTYPE = "form-data";
|
||||||
|
private static final String MULTIPART_FORM_DATA = MULTIPART_TYPE + "/" + DEFAULT_SUBTYPE;
|
||||||
|
private Queue<AudioRecording> mAudioRecordingQueue = new LinkedTransferQueue<>();
|
||||||
|
private ScheduledFuture<?> mAudioRecordingProcessorFuture;
|
||||||
|
private HttpClient mHttpClient = HttpClient.newBuilder()
|
||||||
|
.version(HttpClient.Version.HTTP_2)
|
||||||
|
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||||
|
.connectTimeout(Duration.ofSeconds(20))
|
||||||
|
.build();
|
||||||
|
private long mLastConnectionAttempt;
|
||||||
|
private long mConnectionAttemptInterval = 5000; //Every 5 seconds
|
||||||
|
private AliasModel mAliasModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance of the broadcaster
|
||||||
|
* @param config to use
|
||||||
|
* @param aliasModel for access to aliases
|
||||||
|
*/
|
||||||
|
public RdioScannerBroadcaster(RdioScannerConfiguration config, InputAudioFormat inputAudioFormat,
|
||||||
|
MP3Setting mp3Setting, AliasModel aliasModel)
|
||||||
|
{
|
||||||
|
super(config);
|
||||||
|
mAliasModel = aliasModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the audio recording processor thread
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTING);
|
||||||
|
String response = testConnection(getBroadcastConfiguration());
|
||||||
|
mLastConnectionAttempt = System.currentTimeMillis();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rdio Scanner API does not currently expose a test method.
|
||||||
|
*/
|
||||||
|
if(response != null && response.toLowerCase().startsWith("incomplete call data: no talkgroup"))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("Error connecting to Rdio Scanner server on startup [" + response + "]");
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mAudioRecordingProcessorFuture == null)
|
||||||
|
{
|
||||||
|
mAudioRecordingProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new AudioRecordingProcessor(),
|
||||||
|
0, 500, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the audio recording processor thread
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
if(mAudioRecordingProcessorFuture != null)
|
||||||
|
{
|
||||||
|
mAudioRecordingProcessorFuture.cancel(true);
|
||||||
|
mAudioRecordingProcessorFuture = null;
|
||||||
|
dispose();
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares for disposal
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
AudioRecording audioRecording = mAudioRecordingQueue.poll();
|
||||||
|
|
||||||
|
while(audioRecording != null)
|
||||||
|
{
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
audioRecording = mAudioRecordingQueue.poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this broadcaster continues to have successful connections to and transactions with the remote
|
||||||
|
* server. If there is a connectivity or other issue, the broadcast state is set to temporary error and
|
||||||
|
* the audio processor thread will persistently invoke this method to attempt a reconnect.
|
||||||
|
*
|
||||||
|
* Rdio Scanner does not have a test API endpoint, so we look for the incomplete call response.
|
||||||
|
*/
|
||||||
|
private boolean connected()
|
||||||
|
{
|
||||||
|
if(getBroadcastState() != BroadcastState.CONNECTED &&
|
||||||
|
(System.currentTimeMillis() - mLastConnectionAttempt > mConnectionAttemptInterval))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTING);
|
||||||
|
|
||||||
|
String response = testConnection(getBroadcastConfiguration());
|
||||||
|
mLastConnectionAttempt = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if(response != null && response.toLowerCase().startsWith("incomplete call data: no talkgroup"))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBroadcastState() == BroadcastState.CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAudioQueueSize()
|
||||||
|
{
|
||||||
|
return mAudioRecordingQueue.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
mAudioRecordingQueue.offer(audioRecording);
|
||||||
|
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the audio recording is non-null and not too old, meaning that the age of the recording has not
|
||||||
|
* exceeded the max age value indicated in the broadcast configuration. Audio recordings that are too old will be
|
||||||
|
* deleted to ensure that the in-memory queue size doesn't blow up.
|
||||||
|
* @param audioRecording to test
|
||||||
|
* @return true if the recording is valid
|
||||||
|
*/
|
||||||
|
private boolean isValid(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
return audioRecording != null && System.currentTimeMillis() - audioRecording.getStartTime() <=
|
||||||
|
getBroadcastConfiguration().getMaximumRecordingAge();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes any enqueued audio recordings. This method employs asynchronous
|
||||||
|
* interaction with the server, so multiple audio recording uploads can occur simultaneously.
|
||||||
|
*/
|
||||||
|
private void processRecordingQueue()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
while(connected() && !mAudioRecordingQueue.isEmpty())
|
||||||
|
{
|
||||||
|
final AudioRecording audioRecording = mAudioRecordingQueue.poll();
|
||||||
|
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_QUEUE_CHANGE));
|
||||||
|
|
||||||
|
if(isValid(audioRecording) && audioRecording.getRecordingLength() > 0)
|
||||||
|
{
|
||||||
|
float durationSeconds = (float)(audioRecording.getRecordingLength() / 1E3f);
|
||||||
|
long timestampSeconds = (int)(audioRecording.getStartTime() / 1E3);
|
||||||
|
String talkgroup = getTo(audioRecording);
|
||||||
|
String radioId = getFrom(audioRecording);
|
||||||
|
Long frequency = getFrequency(audioRecording);
|
||||||
|
String patches = getPatches(audioRecording);
|
||||||
|
String talkgroupLabel = getTalkgroupLabel(audioRecording);
|
||||||
|
String talkgroupGroup = getTalkgroupGroup(audioRecording);
|
||||||
|
String systemLabel = getSystemLabel(audioRecording);
|
||||||
|
String path = audioRecording.getPath().toString();
|
||||||
|
// Remove TEMPORARY_STREAM_FILE_SUFFIX
|
||||||
|
String audioName = path.substring(path.substring(0, path.lastIndexOf("_")).lastIndexOf("_") + 1);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] audioBytes = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
audioBytes = Files.readAllBytes(audioRecording.getPath());
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
mLog.error("Rdio Scanner API - audio recording file not found - ignoring upload");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(audioBytes != null)
|
||||||
|
{
|
||||||
|
|
||||||
|
RdioScannerBuilder bodyBuilder = new RdioScannerBuilder();
|
||||||
|
bodyBuilder.addPart(FormField.KEY, getBroadcastConfiguration().getApiKey())
|
||||||
|
.addPart(FormField.SYSTEM, getBroadcastConfiguration().getSystemID())
|
||||||
|
.addAudioName(audioName)
|
||||||
|
.addFile(audioBytes)
|
||||||
|
.addPart(FormField.DATE_TIME, timestampSeconds)
|
||||||
|
.addPart(FormField.TALKGROUP_ID, talkgroup)
|
||||||
|
.addPart(FormField.SOURCE, radioId)
|
||||||
|
.addPart(FormField.FREQUENCY, frequency)
|
||||||
|
.addPart(FormField.TALKGROUP_LABEL, talkgroupLabel)
|
||||||
|
.addPart(FormField.TALKGROUP_GROUP, talkgroupGroup)
|
||||||
|
.addPart(FormField.SYSTEM_LABEL, systemLabel)
|
||||||
|
.addPart(FormField.PATCHES, patches);
|
||||||
|
|
||||||
|
HttpRequest fileRequest = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(getBroadcastConfiguration().getHost()))
|
||||||
|
.header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bodyBuilder.getBoundary())
|
||||||
|
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
|
||||||
|
.header(HttpHeaders.CONTENT_TYPE, "audio/mpeg")
|
||||||
|
.POST(bodyBuilder.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
mHttpClient.sendAsync(fileRequest, HttpResponse.BodyHandlers.ofString())
|
||||||
|
.whenComplete((fileResponse, throwable1) -> {
|
||||||
|
if(throwable1 != null || fileResponse.statusCode() != 200)
|
||||||
|
{
|
||||||
|
if(throwable1 instanceof IOException || throwable1 instanceof CompletionException)
|
||||||
|
{
|
||||||
|
//We get socket reset exceptions occasionally when the remote server doesn't
|
||||||
|
//fully read our request and immediately responds.
|
||||||
|
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||||
|
mLog.error("Rdio Scanner API file upload fail [" +
|
||||||
|
fileResponse.statusCode() + "] response [" +
|
||||||
|
fileResponse.body() + "]");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||||
|
mLog.error("Rdio Scanner API file upload fail [" +
|
||||||
|
fileResponse.statusCode() + "] response [" +
|
||||||
|
fileResponse.body() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
incrementErrorAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(RdioScannerBroadcaster.this,
|
||||||
|
BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String fileResponseString = fileResponse.body();
|
||||||
|
|
||||||
|
if(fileResponseString.contains("Call imported successfully."))
|
||||||
|
{
|
||||||
|
incrementStreamedAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(RdioScannerBroadcaster.this,
|
||||||
|
BroadcastEvent.Event.BROADCASTER_STREAMED_COUNT_CHANGE));
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
}
|
||||||
|
else if(fileResponseString.contains("duplicate call rejected"))
|
||||||
|
{
|
||||||
|
//Rdio Scanner is telling us to skip audio upload - someone already uploaded it
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.TEMPORARY_BROADCAST_ERROR);
|
||||||
|
mLog.error("Rdio Scanner API file upload fail [" +
|
||||||
|
fileResponse.statusCode() + "] response [" +
|
||||||
|
fileResponse.body() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Register an error for the file not found exception
|
||||||
|
mLog.error("Rdio Scanner API - upload file not found [" +
|
||||||
|
audioRecording.getPath().toString() + "]");
|
||||||
|
incrementErrorAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(RdioScannerBroadcaster.this,
|
||||||
|
BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Unknown Error", e);
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
incrementErrorAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_ERROR_COUNT_CHANGE));
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we're not connected and there are recordings in the queue, check the recording at the head of the queue
|
||||||
|
// and start age-off once the recordings become too old. The recordings should be time ordered in the queue.
|
||||||
|
AudioRecording audioRecording = mAudioRecordingQueue.peek();
|
||||||
|
|
||||||
|
while(audioRecording != null)
|
||||||
|
{
|
||||||
|
if(isValid(audioRecording))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Remove the recording from the queue, remove a replay, and peek at the next recording in the queue
|
||||||
|
mAudioRecordingQueue.poll();
|
||||||
|
audioRecording.removePendingReplay();
|
||||||
|
incrementAgedOffAudioCount();
|
||||||
|
broadcast(new BroadcastEvent(this, BroadcastEvent.Event.BROADCASTER_AGED_OFF_COUNT_CHANGE));
|
||||||
|
audioRecording = mAudioRecordingQueue.peek();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a frequency value from the audio recording identifier collection.
|
||||||
|
*/
|
||||||
|
private static Long getFrequency(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getIdentifier(IdentifierClass.CONFIGURATION,
|
||||||
|
Form.CHANNEL_FREQUENCY, Role.ANY);
|
||||||
|
|
||||||
|
if(identifier instanceof ConfigurationLongIdentifier)
|
||||||
|
{
|
||||||
|
Long value = ((ConfigurationLongIdentifier)identifier).getValue();
|
||||||
|
|
||||||
|
if(value != null)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Long.valueOf(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the FROM identifier or uses a default of zero(0)
|
||||||
|
*/
|
||||||
|
private static String getFrom(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
for(Identifier identifier: audioRecording.getIdentifierCollection().getIdentifiers(Role.FROM))
|
||||||
|
{
|
||||||
|
if(identifier instanceof RadioIdentifier)
|
||||||
|
{
|
||||||
|
return ((RadioIdentifier)identifier).getValue().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the TO identifiers or uses a default of zero (0)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static String getTo(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||||
|
|
||||||
|
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||||
|
{
|
||||||
|
return patchGroupIdentifier.getValue().getPatchGroup().getValue().toString();
|
||||||
|
}
|
||||||
|
else if(identifier instanceof TalkgroupIdentifier talkgroupIdentifier)
|
||||||
|
{
|
||||||
|
return String.valueOf(RadioReferenceDecoder.convertToRadioReferenceTalkgroup(talkgroupIdentifier.getValue(),
|
||||||
|
talkgroupIdentifier.getProtocol()));
|
||||||
|
}
|
||||||
|
else if(identifier instanceof RadioIdentifier radioIdentifier)
|
||||||
|
{
|
||||||
|
return radioIdentifier.getValue().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the Talkgroup Label from the Audio Recording Alias
|
||||||
|
* If this is a PatchGroup we return only the first label as the primary talkgroup label.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private String getTalkgroupLabel(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
|
||||||
|
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if(identifier != null)
|
||||||
|
{
|
||||||
|
List<Alias> aliases = aliasList.getAliases(identifier);
|
||||||
|
if(!aliases.isEmpty())
|
||||||
|
{
|
||||||
|
sb.append(aliases.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the Talkgroup Group from the Audio Recording Alias
|
||||||
|
* If this is a PatchGroup we return only the first group as the primary talkgroup group.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private String getTalkgroupGroup(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
|
||||||
|
AliasList aliasList = mAliasModel.getAliasList(audioRecording.getIdentifierCollection());
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if(identifier != null)
|
||||||
|
{
|
||||||
|
List<Alias> aliases = aliasList.getAliases(identifier);
|
||||||
|
if(!aliases.isEmpty())
|
||||||
|
{
|
||||||
|
sb.append(aliases.get(0).getGroup());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the System Label from the Audio Recording Alias
|
||||||
|
* If this is a PatchGroup we return only the first sytem as the primary talkgroup system.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private String getSystemLabel(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
List<Identifier> systems = audioRecording.getIdentifierCollection().getIdentifiers(Form.SYSTEM);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if(!systems.isEmpty())
|
||||||
|
{
|
||||||
|
sb.append(systems.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a formatted string with the patched talkgroups
|
||||||
|
*/
|
||||||
|
public static String getPatches(AudioRecording audioRecording)
|
||||||
|
{
|
||||||
|
Identifier identifier = audioRecording.getIdentifierCollection().getToIdentifier();
|
||||||
|
|
||||||
|
if(identifier instanceof PatchGroupIdentifier patchGroupIdentifier)
|
||||||
|
{
|
||||||
|
PatchGroup patchGroup = patchGroupIdentifier.getValue();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("[");
|
||||||
|
|
||||||
|
sb.append(patchGroup.getPatchGroup().getValue().toString());
|
||||||
|
|
||||||
|
for(TalkgroupIdentifier patched: patchGroup.getPatchedTalkgroupIdentifiers())
|
||||||
|
{
|
||||||
|
sb.append(",").append(patched.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
for(RadioIdentifier patched: patchGroup.getPatchedRadioIdentifiers())
|
||||||
|
{
|
||||||
|
sb.append(",").append(patched.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "[]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests both the connection and configuration against the RdioScanner Call API service
|
||||||
|
* @param configuration containing API key and system id
|
||||||
|
* @return error string or null if test is successful
|
||||||
|
*/
|
||||||
|
public static String testConnection(RdioScannerConfiguration configuration)
|
||||||
|
{
|
||||||
|
HttpClient httpClient = HttpClient.newBuilder()
|
||||||
|
.version(HttpClient.Version.HTTP_1_1)
|
||||||
|
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||||
|
.connectTimeout(Duration.ofSeconds(20))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
RdioScannerBuilder bodyBuilder = new RdioScannerBuilder();
|
||||||
|
bodyBuilder.addPart(FormField.KEY, configuration.getApiKey())
|
||||||
|
.addPart(FormField.SYSTEM, configuration.getSystemID())
|
||||||
|
.addPart(FormField.TEST, 1);
|
||||||
|
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(configuration.getHost()))
|
||||||
|
.header(HttpHeaders.CONTENT_TYPE, MULTIPART_FORM_DATA + "; boundary=" + bodyBuilder.getBoundary())
|
||||||
|
.header(HttpHeaders.USER_AGENT, "sdrtrunk")
|
||||||
|
.header(HttpHeaders.ACCEPT, "*/*")
|
||||||
|
.POST(bodyBuilder.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
HttpResponse.BodyHandler<String> responseHandler = HttpResponse.BodyHandlers.ofString();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpResponse<String> response = httpClient.send(request, responseHandler);
|
||||||
|
String responseBody = response.body();
|
||||||
|
return (responseBody != null ? responseBody : "(no response)") + " Status Code:" + response.statusCode();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
return e.getLocalizedMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AudioRecordingProcessor implements Runnable
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
processRecordingQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
mLog.debug("Starting ...");
|
||||||
|
|
||||||
|
RdioScannerConfiguration config = new RdioScannerConfiguration();
|
||||||
|
config.setHost("https://api.RdioScanner.com/call-upload-dev");
|
||||||
|
config.setApiKey("c33aae37-8572-11ea-bd8b-0ecc8ab9ccec");
|
||||||
|
config.setSystemID(11);
|
||||||
|
|
||||||
|
String response = testConnection(config);
|
||||||
|
|
||||||
|
if(response == null)
|
||||||
|
{
|
||||||
|
mLog.debug("Test Successful!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(response.contains("1 Invalid-API-Key"))
|
||||||
|
{
|
||||||
|
mLog.error("Invalid API Key");
|
||||||
|
}
|
||||||
|
else if(response.contains("1 API-Key-Access-Denied"))
|
||||||
|
{
|
||||||
|
mLog.error("System ID not valid for API Key");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.debug("Response: " + response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.debug("Finished!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.github.dsheirer.audio.broadcast.rdioscanner;
|
||||||
|
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.lang.*;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder for an HTTP body publisher to produce a RdioScanner call event
|
||||||
|
*/
|
||||||
|
public class RdioScannerBuilder
|
||||||
|
{
|
||||||
|
private static final String DASH_DASH = "--";
|
||||||
|
private static final String BOUNDARY = "--sdrtrunk-sdrtrunk-sdrtrunk";
|
||||||
|
private List<Part> mParts = new ArrayList<>();
|
||||||
|
private byte[] audioBytes = null;
|
||||||
|
private String audioName = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public RdioScannerBuilder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access the static multi-part boundary string
|
||||||
|
*/
|
||||||
|
public String getBoundary()
|
||||||
|
{
|
||||||
|
return BOUNDARY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a Audio file name part to the call
|
||||||
|
*/
|
||||||
|
public RdioScannerBuilder addAudioName(String value)
|
||||||
|
{
|
||||||
|
audioName = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a Audio bytes part to the call
|
||||||
|
*/
|
||||||
|
public RdioScannerBuilder addFile(byte[] value)
|
||||||
|
{
|
||||||
|
audioBytes = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a string part to the call
|
||||||
|
*/
|
||||||
|
public RdioScannerBuilder addPart(FormField key, String value)
|
||||||
|
{
|
||||||
|
if(key != null && value != null)
|
||||||
|
{
|
||||||
|
mParts.add(new Part(key.getHeader(), value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a number part to the call
|
||||||
|
*/
|
||||||
|
public RdioScannerBuilder addPart(FormField key, Number value)
|
||||||
|
{
|
||||||
|
if(key != null && value != null)
|
||||||
|
{
|
||||||
|
mParts.add(new Part(key.getHeader(), value.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a form-data item
|
||||||
|
*/
|
||||||
|
private static String formatPart(Part part, String boundary)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(DASH_DASH).append(boundary).append("\r\n");
|
||||||
|
sb.append("Content-Disposition: form-data; name=\"").append(part.mKey).append("\"\r\n\r\n");
|
||||||
|
sb.append(part.getValue()).append("\r\n");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the audio file item
|
||||||
|
*/
|
||||||
|
private String formatFilePart(String boundary)
|
||||||
|
{
|
||||||
|
|
||||||
|
if(audioBytes == null)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
StringBuilder sb= new StringBuilder();
|
||||||
|
sb.append(DASH_DASH).append(boundary).append("\r\n");
|
||||||
|
sb.append("Content-Disposition: form-data; filename =\"").append(audioName);
|
||||||
|
sb.append("\"; name=\"").append("audio").append("\"\r\n\r\n");
|
||||||
|
return sb.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the boundary closing item
|
||||||
|
*/
|
||||||
|
private static String getClosingBoundary(String boundary)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(DASH_DASH).append(boundary).append(DASH_DASH).append("\r\n");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a BodyPublisher for accessing the call form data
|
||||||
|
*/
|
||||||
|
public HttpRequest.BodyPublisher build()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for(Part part: mParts)
|
||||||
|
{
|
||||||
|
sb.append(formatPart(part, BOUNDARY));
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(formatFilePart(BOUNDARY));
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to create a ByteArray consisting of the Sting "parts" and the audio file bytes
|
||||||
|
*/
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
outputStream.write(sb.toString().getBytes());
|
||||||
|
|
||||||
|
if(audioBytes != null)
|
||||||
|
{
|
||||||
|
outputStream.write(audioBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb = new StringBuilder();
|
||||||
|
sb.append("\r\n");
|
||||||
|
sb.append(getClosingBoundary(BOUNDARY));
|
||||||
|
|
||||||
|
outputStream.write(sb.toString().getBytes());
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
//mLog.error("Rdio Scanner API - unable to create POST reqeust.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpRequest.BodyPublishers.ofByteArray(outputStream.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for(Part part: mParts)
|
||||||
|
{
|
||||||
|
sb.append(formatPart(part, BOUNDARY));
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(getClosingBoundary(BOUNDARY));
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key:Value pair holder
|
||||||
|
*/
|
||||||
|
public class Part
|
||||||
|
{
|
||||||
|
private String mKey;
|
||||||
|
private String mValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new part
|
||||||
|
* @param key value
|
||||||
|
* @param value item
|
||||||
|
*/
|
||||||
|
public Part(String key, String value)
|
||||||
|
{
|
||||||
|
mKey = key;
|
||||||
|
mValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey()
|
||||||
|
{
|
||||||
|
return mKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue()
|
||||||
|
{
|
||||||
|
return mValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.audio.broadcast.rdioscanner;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Streaming configuration for Rdio Scanner API.
|
||||||
|
*
|
||||||
|
* Note: this API is not a streaming audio service, rather a completed call push service. However, it fits nicely
|
||||||
|
* with the structure of the audio streaming subsystem in sdrtrunk
|
||||||
|
*/
|
||||||
|
public class RdioScannerConfiguration extends BroadcastConfiguration
|
||||||
|
{
|
||||||
|
private IntegerProperty mSystemID = new SimpleIntegerProperty();
|
||||||
|
private StringProperty mApiKey = new SimpleStringProperty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for faster jackson
|
||||||
|
*/
|
||||||
|
public RdioScannerConfiguration()
|
||||||
|
{
|
||||||
|
this(BroadcastFormat.MP3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public constructor.
|
||||||
|
* @param format to use for audio recording (MP3)
|
||||||
|
*/
|
||||||
|
public RdioScannerConfiguration(BroadcastFormat format)
|
||||||
|
{
|
||||||
|
super(format);
|
||||||
|
|
||||||
|
//The parent class binds this property, so we unbind it and rebind it here
|
||||||
|
mValid.unbind();
|
||||||
|
mValid.bind(Bindings.and(Bindings.and(Bindings.greaterThan(mSystemID, 0), Bindings.isNotNull(mApiKey)),
|
||||||
|
Bindings.isNotNull(mHost)));
|
||||||
|
|
||||||
|
if(mHost.getValue() == null || mHost.getValue().isEmpty())
|
||||||
|
{
|
||||||
|
mHost.set("http://localhost");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System ID as a property
|
||||||
|
*/
|
||||||
|
public IntegerProperty systemIDProperty()
|
||||||
|
{
|
||||||
|
return mSystemID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API key as a property
|
||||||
|
*/
|
||||||
|
public StringProperty apiKeyProperty()
|
||||||
|
{
|
||||||
|
return mApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Key
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "api_key")
|
||||||
|
public String getApiKey()
|
||||||
|
{
|
||||||
|
return mApiKey.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the api key
|
||||||
|
* @param apiKey
|
||||||
|
*/
|
||||||
|
public void setApiKey(String apiKey)
|
||||||
|
{
|
||||||
|
mApiKey.setValue(apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System ID as provided by RdioScanner.com
|
||||||
|
*/
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "system_id")
|
||||||
|
public int getSystemID()
|
||||||
|
{
|
||||||
|
return mSystemID.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the system ID provided by RdioScanner.com
|
||||||
|
*/
|
||||||
|
public void setSystemID(int systemID)
|
||||||
|
{
|
||||||
|
mSystemID.set(systemID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||||
|
@Override
|
||||||
|
public BroadcastServerType getBroadcastServerType()
|
||||||
|
{
|
||||||
|
return BroadcastServerType.RDIOSCANNER_CALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BroadcastConfiguration copyOf()
|
||||||
|
{
|
||||||
|
RdioScannerConfiguration copy = new RdioScannerConfiguration();
|
||||||
|
copy.setSystemID(getSystemID());
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * ******************************************************************************
|
||||||
|
* * Copyright (C) 2014-2020 Dennis Sheirer
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * (at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* * *****************************************************************************
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package io.github.dsheirer.audio.broadcast.rdioscanner;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
|
||||||
|
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
|
||||||
|
import io.github.dsheirer.audio.broadcast.icecast.IcecastTCPConfiguration;
|
||||||
|
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class RdioScannerFeedConfiguration extends IcecastTCPConfiguration
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(RdioScannerFeedConfiguration.class);
|
||||||
|
|
||||||
|
private int mFeedID;
|
||||||
|
|
||||||
|
public RdioScannerFeedConfiguration()
|
||||||
|
{
|
||||||
|
//No-arg constructor for JAXB
|
||||||
|
this(BroadcastFormat.MP3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RdioScanner configuration for Icecast 2.3.2 compatible servers
|
||||||
|
*
|
||||||
|
* @param format of output audio (MP3)
|
||||||
|
*/
|
||||||
|
public RdioScannerFeedConfiguration(BroadcastFormat format)
|
||||||
|
{
|
||||||
|
super(format);
|
||||||
|
|
||||||
|
setBitRate(16);
|
||||||
|
setChannels(1);
|
||||||
|
setSampleRate(8000);
|
||||||
|
setInline(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RdioScannerFeedConfiguration from(UserFeedBroadcast userFeedBroadcast)
|
||||||
|
{
|
||||||
|
RdioScannerFeedConfiguration config = new RdioScannerFeedConfiguration(BroadcastFormat.MP3);
|
||||||
|
config.setName(userFeedBroadcast.getDescription());
|
||||||
|
config.setHost(userFeedBroadcast.getHostname());
|
||||||
|
config.setMountPoint(userFeedBroadcast.getMount());
|
||||||
|
config.setFeedID(userFeedBroadcast.getFeedId());
|
||||||
|
config.setPassword(userFeedBroadcast.getPassword());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
config.setPort(Integer.parseInt(userFeedBroadcast.getPort()));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error creating Rdio Scanner configuration from radio reference user feed instance");
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BroadcastConfiguration copyOf()
|
||||||
|
{
|
||||||
|
RdioScannerFeedConfiguration copy = new RdioScannerFeedConfiguration(getBroadcastFormat());
|
||||||
|
|
||||||
|
//Broadcast Configuration Parameters
|
||||||
|
copy.setName(getName());
|
||||||
|
copy.setHost(getHost());
|
||||||
|
copy.setPort(getPort());
|
||||||
|
copy.setInline(getInline());
|
||||||
|
copy.setPassword(getPassword());
|
||||||
|
copy.setDelay(getDelay());
|
||||||
|
copy.setEnabled(false);
|
||||||
|
|
||||||
|
//Icecast Configuration Parameters
|
||||||
|
copy.setUserName(getUserName());
|
||||||
|
copy.setMountPoint(getMountPoint());
|
||||||
|
copy.setDescription(getDescription());
|
||||||
|
copy.setGenre(getGenre());
|
||||||
|
copy.setPublic(isPublic());
|
||||||
|
copy.setURL(getURL());
|
||||||
|
|
||||||
|
//RdioScanner Configuration Parameters
|
||||||
|
copy.setFeedID(getFeedID());
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance")
|
||||||
|
@Override
|
||||||
|
public BroadcastServerType getBroadcastServerType()
|
||||||
|
{
|
||||||
|
return BroadcastServerType.RDIOSCANNER_CALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JacksonXmlProperty(isAttribute = true, localName = "feed_id")
|
||||||
|
public int getFeedID()
|
||||||
|
{
|
||||||
|
return mFeedID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFeedID(int feedID)
|
||||||
|
{
|
||||||
|
mFeedID = feedID;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,6 +79,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
private boolean mRunning = false;
|
private boolean mRunning = false;
|
||||||
private ScheduledExecutorService mScheduledExecutorService;
|
private ScheduledExecutorService mScheduledExecutorService;
|
||||||
private ScheduledFuture<?> mProcessorFuture;
|
private ScheduledFuture<?> mProcessorFuture;
|
||||||
|
private boolean mDropDuplicates;
|
||||||
private long mOutputLastTimestamp = 0;
|
private long mOutputLastTimestamp = 0;
|
||||||
private static final long STALE_PLAYBACK_THRESHOLD_MS = 500;
|
private static final long STALE_PLAYBACK_THRESHOLD_MS = 500;
|
||||||
|
|
||||||
@@ -104,6 +105,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory(
|
mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new NamingThreadFactory(
|
||||||
"sdrtrunk audio output " + mixerChannel.name()));
|
"sdrtrunk audio output " + mixerChannel.name()));
|
||||||
mUserPreferences = userPreferences;
|
mUserPreferences = userPreferences;
|
||||||
|
mDropDuplicates = mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -163,7 +165,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
|
|
||||||
updateToneInsertionAudioClips();
|
updateToneInsertionAudioClips();
|
||||||
|
|
||||||
//Register to receive directory preference update notifications so we can update the preference items
|
//Register to receive preference update notifications so we can update the preference items
|
||||||
MyEventBus.getGlobalEventBus().register(this);
|
MyEventBus.getGlobalEventBus().register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +228,10 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
{
|
{
|
||||||
updateToneInsertionAudioClips();
|
updateToneInsertionAudioClips();
|
||||||
}
|
}
|
||||||
|
else if(preferenceType == PreferenceType.DUPLICATE_CALL_DETECTION)
|
||||||
|
{
|
||||||
|
mDropDuplicates = mUserPreferences.getDuplicateCallDetectionPreference().isDuplicatePlaybackSuppressionEnabled();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -301,6 +307,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
mCurrentAudioSegment.decrementConsumerCount();
|
mCurrentAudioSegment.decrementConsumerCount();
|
||||||
mCurrentAudioSegment.removeIdentifierUpdateNotificationListener(this);
|
mCurrentAudioSegment.removeIdentifierUpdateNotificationListener(this);
|
||||||
mCurrentAudioSegment = null;
|
mCurrentAudioSegment = null;
|
||||||
|
broadcast(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,13 +347,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
{
|
{
|
||||||
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
|
mCurrentAudioSegment.addIdentifierUpdateNotificationListener(this);
|
||||||
broadcast(mCurrentAudioSegment.getIdentifierCollection());
|
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;
|
mCurrentBufferIndex = 0;
|
||||||
@@ -359,10 +359,7 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
*/
|
*/
|
||||||
private boolean isThrowaway(AudioSegment audioSegment)
|
private boolean isThrowaway(AudioSegment audioSegment)
|
||||||
{
|
{
|
||||||
return audioSegment != null &&
|
return audioSegment != null && (audioSegment.isDoNotMonitor() || mDropDuplicates && (audioSegment.isDuplicate()));
|
||||||
(audioSegment.isDoNotMonitor() ||
|
|
||||||
(audioSegment.isDuplicate() && mUserPreferences.getDuplicateCallDetectionPreference()
|
|
||||||
.isDuplicatePlaybackSuppressionEnabled()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -378,8 +375,8 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
loadNextAudioSegment();
|
loadNextAudioSegment();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Reevaluate current audio segment to see if the status has changed for duplicate or do-not-monitor.
|
//Evaluate current audio segment to see if the status has changed for duplicate or do-not-monitor.
|
||||||
if(isThrowaway(mCurrentAudioSegment))
|
while(isThrowaway(mCurrentAudioSegment))
|
||||||
{
|
{
|
||||||
if(mCurrentBufferIndex > 0)
|
if(mCurrentBufferIndex > 0)
|
||||||
{
|
{
|
||||||
@@ -392,24 +389,42 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
|
|
||||||
while(mCurrentAudioSegment != null && mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount())
|
while(mCurrentAudioSegment != null && mCurrentBufferIndex < mCurrentAudioSegment.getAudioBufferCount())
|
||||||
{
|
{
|
||||||
try
|
//Continuously evaluate current audio segment to see if the status has changed for duplicate or do-not-monitor.
|
||||||
|
if(isThrowaway(mCurrentAudioSegment))
|
||||||
{
|
{
|
||||||
float[] audioBuffer = mCurrentAudioSegment.getAudioBuffers().get(mCurrentBufferIndex++);
|
if(mCurrentBufferIndex > 0)
|
||||||
|
|
||||||
if(audioBuffer != null)
|
|
||||||
{
|
{
|
||||||
ByteBuffer audio = convert(audioBuffer);
|
playAudio(mAudioSegmentDropTone);
|
||||||
//This call blocks until all audio bytes are dumped into the data line.
|
|
||||||
playAudio(audio);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disposeCurrentAudioSegment();
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
else
|
||||||
{
|
{
|
||||||
mLog.error("Error while processing audio for [" + mMixerChannel.name() + "]", e);
|
if(mCurrentBufferIndex == 0)
|
||||||
|
{
|
||||||
|
playAudio(mAudioSegmentStartTone);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
float[] audioBuffer = mCurrentAudioSegment.getAudioBuffers().get(mCurrentBufferIndex++);
|
||||||
|
|
||||||
|
if(audioBuffer != null)
|
||||||
|
{
|
||||||
|
ByteBuffer audio = convert(audioBuffer);
|
||||||
|
//This call blocks until all audio bytes are dumped into the data line.
|
||||||
|
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
|
//Check for completed and fully-played audio segment to closeout
|
||||||
if(mCurrentAudioSegment != null &&
|
if(mCurrentAudioSegment != null &&
|
||||||
mCurrentAudioSegment.isComplete() &&
|
mCurrentAudioSegment.isComplete() &&
|
||||||
(mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))
|
(mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))
|
||||||
@@ -651,16 +666,6 @@ public abstract class AudioOutput implements LineListener, Listener<IdentifierUp
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//TODO: Debug hooks - remove after testing
|
|
||||||
if(mMixerChannel.name().equals("LEFT"))
|
|
||||||
{
|
|
||||||
int a = 0;
|
|
||||||
}
|
|
||||||
else if(mMixerChannel.name().equals("RIGHT"))
|
|
||||||
{
|
|
||||||
int a = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
processAudio();
|
processAudio();
|
||||||
}
|
}
|
||||||
catch(Throwable t)
|
catch(Throwable t)
|
||||||
|
|||||||
@@ -276,8 +276,7 @@ public class BinaryMessage extends BitSet
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new BitSetFullException("bitset is full -- contains " +
|
throw new BitSetFullException("bitset is full -- contains [" + mPointer + "/" + size() + "] bits");
|
||||||
(mPointer + 1) + "bits");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -845,13 +844,18 @@ public class BinaryMessage extends BitSet
|
|||||||
*/
|
*/
|
||||||
public int getTwosComplement(int start, int end)
|
public int getTwosComplement(int start, int end)
|
||||||
{
|
{
|
||||||
BinaryMessage sub = getSubMessage(start, end);
|
if(get(start))
|
||||||
boolean negative = sub.get(start);
|
{
|
||||||
sub.flip(0, sub.size());
|
//Negative value - flip and add one
|
||||||
|
BinaryMessage fragment = getSubMessage(start, end);
|
||||||
int value = sub.getInt(0, sub.size()) + 1;
|
fragment.flip(0, fragment.size());
|
||||||
value *= (negative ? -1 : 1);
|
return fragment.getInt(1, fragment.size()) + 1;
|
||||||
return value;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Positive value - return the contents.
|
||||||
|
return getInt(start + 1, end);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -132,12 +132,10 @@ public enum SyncPattern
|
|||||||
true, true, false, false //1100 0xC
|
true, true, false, false //1100 0xC
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/* Sync (0x158) = 1101011000 */
|
/* Sync (0x158) = 101011000 */
|
||||||
PASSPORT( new boolean[]
|
PASSPORT( new boolean[]
|
||||||
{
|
{
|
||||||
true, //0001 0x1
|
true,false,true,false,true,true,false,false,false
|
||||||
false, true, false, true, //0101 0x5
|
|
||||||
true, false, false, false //1000 0x8
|
|
||||||
} ),
|
} ),
|
||||||
|
|
||||||
LTR_STANDARD_OSW( new boolean[]
|
LTR_STANDARD_OSW( new boolean[]
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -84,7 +84,7 @@ public class DcCorrectionManager
|
|||||||
{
|
{
|
||||||
mDcCalculationsRemaining--;
|
mDcCalculationsRemaining--;
|
||||||
}
|
}
|
||||||
//
|
|
||||||
// mLog.info("DC: " + mDecimalFormat.format(mAverageDc) +
|
// mLog.info("DC: " + mDecimalFormat.format(mAverageDc) +
|
||||||
// " RES:" + mDecimalFormat.format(residualOffsetToRemove) +
|
// " RES:" + mDecimalFormat.format(residualOffsetToRemove) +
|
||||||
// " REM:" + mDcCalculationsRemaining);
|
// " REM:" + mDcCalculationsRemaining);
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -21,21 +21,16 @@ package io.github.dsheirer.buffer;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Circular sample buffer - allocates a buffer and stores samples in a circular
|
* Circular sample buffer - allocates a buffer and stores samples in a circular fashion, overwriting older samples with
|
||||||
* fashion, overwriting older samples with newly arrived samples. Initially
|
* newly arrived samples. Initially fills buffer with 0-valued (default) samples or with a specified value.
|
||||||
* fills buffer with 0-valued samples
|
|
||||||
*
|
|
||||||
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
|
|
||||||
*/
|
*/
|
||||||
public class FloatCircularBuffer
|
public class FloatCircularBuffer
|
||||||
{
|
{
|
||||||
private float[] mBuffer;
|
private float[] mBuffer;
|
||||||
private int mBufferPointer = 0;
|
private int mBufferPointer = 0;
|
||||||
private float mOldestSample;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a circular double buffer of the specified size and all entries filled with the specified initial value.
|
* Creates a circular double buffer of the specified size and all entries filled with the specified initial value.
|
||||||
*
|
|
||||||
* @param size of the buffer
|
* @param size of the buffer
|
||||||
* @param initialFillValue to fill the buffer entries
|
* @param initialFillValue to fill the buffer entries
|
||||||
*/
|
*/
|
||||||
@@ -47,7 +42,6 @@ public class FloatCircularBuffer
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a circular double buffer of the specified size and all entries filled with an initial value of zero.
|
* Creates a circular double buffer of the specified size and all entries filled with an initial value of zero.
|
||||||
*
|
|
||||||
* @param size of the buffer
|
* @param size of the buffer
|
||||||
*/
|
*/
|
||||||
public FloatCircularBuffer(int size)
|
public FloatCircularBuffer(int size)
|
||||||
@@ -55,33 +49,34 @@ public class FloatCircularBuffer
|
|||||||
this(size, 0.0f);
|
this(size, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float[] getBuffer()
|
/**
|
||||||
|
* Resets the delay buffer to an all zeros state
|
||||||
|
*/
|
||||||
|
public void reset(float fillValue)
|
||||||
{
|
{
|
||||||
return mBuffer;
|
Arrays.fill(mBuffer, fillValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current buffer value and overwrites that value position in the buffer with the new value.
|
* Puts the new value into the buffer and returns the oldest buffer value that it replaced
|
||||||
*
|
*
|
||||||
* @param newValue to add to the buffer
|
* @param newValue to add to the buffer
|
||||||
* @return oldest value that was overwritten by the new value
|
* @return oldest value that was overwritten by the new value
|
||||||
*/
|
*/
|
||||||
public float getAndPut(float newValue)
|
public float get(float newValue)
|
||||||
{
|
{
|
||||||
mOldestSample = mBuffer[mBufferPointer];
|
float oldestSample = mBuffer[mBufferPointer];
|
||||||
put(newValue);
|
|
||||||
return mOldestSample;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
mBuffer[mBufferPointer] = newValue;
|
||||||
* Puts the value into the buffer, updates the pointer and returns the buffer value at the pointer position.
|
|
||||||
* @param sample to add
|
mBufferPointer++;
|
||||||
* @return next pointed sample.
|
|
||||||
*/
|
if(mBufferPointer >= mBuffer.length)
|
||||||
public float putAndGet(float sample)
|
{
|
||||||
{
|
mBufferPointer = 0;
|
||||||
put(sample);
|
}
|
||||||
return mBuffer[mBufferPointer];
|
|
||||||
|
return oldestSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float[] getAll()
|
public float[] getAll()
|
||||||
@@ -105,13 +100,70 @@ public class FloatCircularBuffer
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Places the new value into the buffer, overwriting the oldest value
|
* Places the new value into the buffer, overwriting the oldest value
|
||||||
*
|
|
||||||
* @param newValue to add to the buffer
|
* @param newValue to add to the buffer
|
||||||
*/
|
*/
|
||||||
public void put(float newValue)
|
public void put(float newValue)
|
||||||
{
|
{
|
||||||
mBuffer[mBufferPointer] = newValue;
|
mBuffer[mBufferPointer] = newValue;
|
||||||
|
|
||||||
mBufferPointer++;
|
mBufferPointer++;
|
||||||
mBufferPointer %= mBuffer.length;
|
|
||||||
|
if(mBufferPointer >= mBuffer.length)
|
||||||
|
{
|
||||||
|
mBufferPointer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum from the values currently in the buffer
|
||||||
|
*/
|
||||||
|
public float max(float referenceValue)
|
||||||
|
{
|
||||||
|
float max = referenceValue;
|
||||||
|
|
||||||
|
for(int x = 0; x < mBuffer.length; x++)
|
||||||
|
{
|
||||||
|
if(mBuffer[x] > max)
|
||||||
|
{
|
||||||
|
max = mBuffer[x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System.out.println("Max [" + max + "] from " + Arrays.toString(mBuffer));
|
||||||
|
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the minimum from the values currently in the buffer
|
||||||
|
*/
|
||||||
|
public float min()
|
||||||
|
{
|
||||||
|
float min = 0.0f;
|
||||||
|
|
||||||
|
for(int x = 0; x < mBuffer.length; x++)
|
||||||
|
{
|
||||||
|
if(mBuffer[x] < min)
|
||||||
|
{
|
||||||
|
min = mBuffer[x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the average/mean of the values contained in this buffer
|
||||||
|
*/
|
||||||
|
public float mean()
|
||||||
|
{
|
||||||
|
double accumulator = 0.0;
|
||||||
|
|
||||||
|
for(float value: mBuffer)
|
||||||
|
{
|
||||||
|
accumulator += value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (float)(accumulator / mBuffer.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ package io.github.dsheirer.buffer;
|
|||||||
import io.github.dsheirer.sample.SampleUtils;
|
import io.github.dsheirer.sample.SampleUtils;
|
||||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,6 +32,7 @@ import java.util.Iterator;
|
|||||||
public class FloatNativeBuffer extends AbstractNativeBuffer
|
public class FloatNativeBuffer extends AbstractNativeBuffer
|
||||||
{
|
{
|
||||||
private float[] mInterleavedComplexSamples;
|
private float[] mInterleavedComplexSamples;
|
||||||
|
private static final int BUFFER_SIZE = 2048;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
@@ -81,48 +84,51 @@ public class FloatNativeBuffer extends AbstractNativeBuffer
|
|||||||
|
|
||||||
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
|
private class ComplexSamplesIterator implements Iterator<ComplexSamples>
|
||||||
{
|
{
|
||||||
private boolean mEmpty;
|
private int mBufferPointer = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasNext()
|
public boolean hasNext()
|
||||||
{
|
{
|
||||||
return !mEmpty;
|
return mBufferPointer < mInterleavedComplexSamples.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ComplexSamples next()
|
public ComplexSamples next()
|
||||||
{
|
{
|
||||||
if(mEmpty)
|
if(!hasNext())
|
||||||
{
|
{
|
||||||
throw new IllegalStateException("No more samples");
|
throw new IllegalStateException("No more samples");
|
||||||
}
|
}
|
||||||
|
|
||||||
mEmpty = true;
|
float[] chunk = Arrays.copyOfRange(mInterleavedComplexSamples, mBufferPointer, mBufferPointer + BUFFER_SIZE * 2);
|
||||||
return SampleUtils.deinterleave(mInterleavedComplexSamples, getTimestamp());
|
mBufferPointer += BUFFER_SIZE * 2;
|
||||||
|
|
||||||
|
return SampleUtils.deinterleave(chunk, getTimestamp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
|
private class InterleavedComplexSamplesIterator implements Iterator<InterleavedComplexSamples>
|
||||||
{
|
{
|
||||||
private boolean mEmpty;
|
private int mBufferPointer = 0;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasNext()
|
public boolean hasNext()
|
||||||
{
|
{
|
||||||
return !mEmpty;
|
return mBufferPointer < mInterleavedComplexSamples.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InterleavedComplexSamples next()
|
public InterleavedComplexSamples next()
|
||||||
{
|
{
|
||||||
if(mEmpty)
|
if(!hasNext())
|
||||||
{
|
{
|
||||||
throw new IllegalStateException("No more samples");
|
throw new IllegalStateException("No more samples");
|
||||||
}
|
}
|
||||||
|
|
||||||
mEmpty = true;
|
float[] chunk = Arrays.copyOfRange(mInterleavedComplexSamples, mBufferPointer, mBufferPointer + BUFFER_SIZE * 2);
|
||||||
return new InterleavedComplexSamples(mInterleavedComplexSamples, getTimestamp());
|
mBufferPointer += BUFFER_SIZE * 2;
|
||||||
|
|
||||||
|
return new InterleavedComplexSamples(chunk, getTimestamp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
package io.github.dsheirer.buffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Circular sample buffer - allocates a buffer and stores samples in a circular
|
|
||||||
* fashion, overwriting older samples with newly arrived samples.
|
|
||||||
*
|
|
||||||
* Can be used as a delay-type buffer, to delay samples by the 'size' amount
|
|
||||||
*/
|
|
||||||
public class RealCircularBuffer
|
|
||||||
{
|
|
||||||
float[] mBuffer;
|
|
||||||
int mBufferPointer = 0;
|
|
||||||
|
|
||||||
public RealCircularBuffer(int size)
|
|
||||||
{
|
|
||||||
mBuffer = new float[size];
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSize()
|
|
||||||
{
|
|
||||||
return mBuffer.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float[] getBuffer()
|
|
||||||
{
|
|
||||||
return mBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Puts the new value into the buffer and returns the oldest buffer value
|
|
||||||
* that it replaced
|
|
||||||
*
|
|
||||||
* @param newValue
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public float putAndGet(float newValue)
|
|
||||||
{
|
|
||||||
float oldestSample = mBuffer[mBufferPointer];
|
|
||||||
|
|
||||||
put(newValue);
|
|
||||||
|
|
||||||
return oldestSample;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the new value to this buffer.
|
|
||||||
*/
|
|
||||||
public void put(float newValue)
|
|
||||||
{
|
|
||||||
mBuffer[mBufferPointer] = newValue;
|
|
||||||
|
|
||||||
mBufferPointer++;
|
|
||||||
|
|
||||||
if(mBufferPointer >= mBuffer.length)
|
|
||||||
{
|
|
||||||
mBufferPointer = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the sample at the specified index, where index 0 is the oldest sample and index (length - 1) is the
|
|
||||||
* newest sample.
|
|
||||||
* @param index in range of 0 to length-1
|
|
||||||
* @return value at indexed position
|
|
||||||
*/
|
|
||||||
|
|
||||||
public float get(int index)
|
|
||||||
{
|
|
||||||
if(index < mBuffer.length)
|
|
||||||
{
|
|
||||||
int pointer = mBufferPointer + index;
|
|
||||||
|
|
||||||
if(pointer >= mBuffer.length)
|
|
||||||
{
|
|
||||||
pointer -= mBuffer.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mBuffer[pointer];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new ArrayIndexOutOfBoundsException("Index [" + index + "] is not valid for the buffer size of [" +
|
|
||||||
mBuffer.length + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the contents of the circular buffer unwrapped where index 0 is the oldest sample and index length-1
|
|
||||||
* is the newest sample.
|
|
||||||
*
|
|
||||||
* @return array of samples
|
|
||||||
*/
|
|
||||||
public float[] get()
|
|
||||||
{
|
|
||||||
float[] samples = new float[mBuffer.length];
|
|
||||||
|
|
||||||
System.arraycopy(mBuffer, mBufferPointer, samples, 0, mBuffer.length - mBufferPointer);
|
|
||||||
|
|
||||||
if(mBufferPointer != 0)
|
|
||||||
{
|
|
||||||
System.arraycopy(mBuffer, 0, samples, mBuffer.length - mBufferPointer, mBufferPointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return samples;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.buffer.airspy.hf;
|
||||||
|
|
||||||
|
import io.github.dsheirer.buffer.AbstractNativeBuffer;
|
||||||
|
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||||
|
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native buffer implementation for Airspy HF+ & Discovery tuners.
|
||||||
|
*/
|
||||||
|
public class AirspyHfNativeBuffer extends AbstractNativeBuffer
|
||||||
|
{
|
||||||
|
public static final float SCALE = 1.0f / 32768.0f;
|
||||||
|
private short[] mInterleavedSamples;
|
||||||
|
private float mAverageDc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
* @param timestamp of the first sample of the buffer
|
||||||
|
* @param interleavedSamples pairs of 16-bit complex samples.
|
||||||
|
*/
|
||||||
|
public AirspyHfNativeBuffer(long timestamp, float samplesPerMillisecond, float averageDc, short[] interleavedSamples)
|
||||||
|
{
|
||||||
|
super(timestamp, samplesPerMillisecond);
|
||||||
|
mInterleavedSamples = interleavedSamples;
|
||||||
|
mAverageDc = averageDc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<ComplexSamples> iterator()
|
||||||
|
{
|
||||||
|
return new IteratorComplexSamples();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
|
||||||
|
{
|
||||||
|
return new IteratorInterleaved();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int sampleCount()
|
||||||
|
{
|
||||||
|
return mInterleavedSamples.length / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scalar implementation of complex samples buffer iterator
|
||||||
|
*/
|
||||||
|
public class IteratorComplexSamples implements Iterator<ComplexSamples>
|
||||||
|
{
|
||||||
|
private boolean mHasNext = true;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext()
|
||||||
|
{
|
||||||
|
return mHasNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComplexSamples next()
|
||||||
|
{
|
||||||
|
int length = mInterleavedSamples.length / 2;
|
||||||
|
float[] i = new float[length];
|
||||||
|
float[] q = new float[length];
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
for(int x = 0; x < mInterleavedSamples.length; x += 2)
|
||||||
|
{
|
||||||
|
index = x / 2;
|
||||||
|
//Native ordering is in Q, I, Q, I ... reverse it to I, Q, I, Q
|
||||||
|
i[index] = (mInterleavedSamples[x + 1] * SCALE) - mAverageDc;
|
||||||
|
q[index] = (mInterleavedSamples[x] * SCALE) - mAverageDc;
|
||||||
|
}
|
||||||
|
|
||||||
|
mHasNext = false;
|
||||||
|
|
||||||
|
return new ComplexSamples(i, q, getTimestamp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scalar implementation of interleaved sample buffer iterator.
|
||||||
|
*/
|
||||||
|
public class IteratorInterleaved implements Iterator<InterleavedComplexSamples>
|
||||||
|
{
|
||||||
|
private boolean mHasNext = true;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext()
|
||||||
|
{
|
||||||
|
return mHasNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InterleavedComplexSamples next()
|
||||||
|
{
|
||||||
|
float[] samples = new float[mInterleavedSamples.length];
|
||||||
|
|
||||||
|
for(int x = 0; x < mInterleavedSamples.length; x += 2)
|
||||||
|
{
|
||||||
|
//Native ordering is in Q, I, Q, I ... reverse it to I, Q, I, Q
|
||||||
|
samples[x] = (mInterleavedSamples[x + 1] * SCALE) - mAverageDc;
|
||||||
|
samples[x + 1] = (mInterleavedSamples[x] * SCALE) - mAverageDc;
|
||||||
|
}
|
||||||
|
|
||||||
|
mHasNext = false;
|
||||||
|
|
||||||
|
return new InterleavedComplexSamples(samples, getTimestamp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.buffer.airspy.hf;
|
||||||
|
|
||||||
|
import io.github.dsheirer.buffer.AbstractNativeBufferFactory;
|
||||||
|
import io.github.dsheirer.buffer.DcCorrectionManager;
|
||||||
|
import io.github.dsheirer.buffer.INativeBuffer;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.ShortBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Airspy HF+ native buffer factory.
|
||||||
|
*
|
||||||
|
* Note: the tuner provides 1024 samples in each transfer where samples are 16-bit interleaved complex.
|
||||||
|
*/
|
||||||
|
public class AirspyHfNativeBufferFactory extends AbstractNativeBufferFactory
|
||||||
|
{
|
||||||
|
private DcCorrectionManager mDcCorrectionManager = new DcCorrectionManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public AirspyHfNativeBufferFactory()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the samples byte buffer into a native buffer and calculates DC offset
|
||||||
|
* @param samples byte array copied from native memory
|
||||||
|
* @param timestamp of the samples
|
||||||
|
* @return constructed native buffer
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public INativeBuffer getBuffer(ByteBuffer samples, long timestamp)
|
||||||
|
{
|
||||||
|
ShortBuffer shortBuffer = samples.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
|
||||||
|
short[] converted = new short[shortBuffer.capacity()];
|
||||||
|
shortBuffer.get(converted);
|
||||||
|
|
||||||
|
if(mDcCorrectionManager.shouldCalculateDc())
|
||||||
|
{
|
||||||
|
calculateDc(converted);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AirspyHfNativeBuffer(timestamp, getSamplesPerMillisecond(), mDcCorrectionManager.getAverageDc(), converted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the average DC in the sample stream so that it can be subtracted from the samples when the
|
||||||
|
* native buffer is used.
|
||||||
|
* @param samples containing DC offset
|
||||||
|
*/
|
||||||
|
private void calculateDc(short[] samples)
|
||||||
|
{
|
||||||
|
float dcAccumulator = 0;
|
||||||
|
|
||||||
|
for(short sample: samples)
|
||||||
|
{
|
||||||
|
dcAccumulator += sample;
|
||||||
|
}
|
||||||
|
|
||||||
|
dcAccumulator /= samples.length;
|
||||||
|
mDcCorrectionManager.adjust(dcAccumulator * AirspyHfNativeBuffer.SCALE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,11 +35,11 @@ import javax.swing.JPanel;
|
|||||||
|
|
||||||
public class NowPlayingPanel extends JPanel
|
public class NowPlayingPanel extends JPanel
|
||||||
{
|
{
|
||||||
private ChannelMetadataPanel mChannelMetadataPanel;
|
private final ChannelMetadataPanel mChannelMetadataPanel;
|
||||||
private ChannelDetailPanel mChannelDetailPanel;
|
private final ChannelDetailPanel mChannelDetailPanel;
|
||||||
private DecodeEventPanel mDecodeEventPanel;
|
private final DecodeEventPanel mDecodeEventPanel;
|
||||||
private MessageActivityPanel mMessageActivityPanel;
|
private final MessageActivityPanel mMessageActivityPanel;
|
||||||
private ChannelPowerPanel mChannelPowerPanel;
|
private final ChannelPowerPanel mChannelPowerPanel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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,
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ public class DecoderStateEvent
|
|||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
sb.append("Decoder State Event - source[").append(mSource.getClass())
|
sb.append("Decoder State Event - source[").append(mSource != null ? mSource.getClass() : "null")
|
||||||
.append("] event[").append(mEvent)
|
.append("] event[").append(mEvent)
|
||||||
.append("] state[").append(mState)
|
.append("] state[").append(mState)
|
||||||
.append("] timeslot[").append(mTimeslot)
|
.append("] timeslot[").append(mTimeslot)
|
||||||
|
|||||||
@@ -0,0 +1,286 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.am;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
|
||||||
|
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
|
||||||
|
import io.github.dsheirer.vector.calibrate.Calibration;
|
||||||
|
import io.github.dsheirer.vector.calibrate.CalibrationException;
|
||||||
|
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||||
|
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||||
|
import org.apache.commons.math3.stat.descriptive.moment.Mean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calibrates AM demodulator options
|
||||||
|
*/
|
||||||
|
public class AmDemodulatorCalibration extends Calibration
|
||||||
|
{
|
||||||
|
private static final int BUFFER_SIZE = 2048;
|
||||||
|
private static final int ITERATION_DURATION_MS = 1000;
|
||||||
|
private static final int WARMUP_ITERATIONS = 5;
|
||||||
|
private static final int TEST_ITERATIONS = 5;
|
||||||
|
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
|
||||||
|
private IAmDemodulator mScalarDemodulator = new ScalarAMDemodulator(500.0f);
|
||||||
|
private IAmDemodulator mVectorDemodulator64 = new VectorAMDemodulator64(500.0f);
|
||||||
|
private IAmDemodulator mVectorDemodulator128 = new VectorAMDemodulator128(500.0f);
|
||||||
|
private IAmDemodulator mVectorAMDemodulator256 = new VectorAMDemodulator256(500.0f);
|
||||||
|
private IAmDemodulator mVectorAMDemodulator512 = new VectorAMDemodulator512(500.0f);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*/
|
||||||
|
public AmDemodulatorCalibration()
|
||||||
|
{
|
||||||
|
super(CalibrationType.AM_DEMODULATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void calibrate() throws CalibrationException
|
||||||
|
{
|
||||||
|
float[] magnitude = getFloatSamples(BUFFER_SIZE);
|
||||||
|
|
||||||
|
Mean scalarMean = new Mean();
|
||||||
|
|
||||||
|
for(int x = 0; x < WARMUP_ITERATIONS; x++)
|
||||||
|
{
|
||||||
|
long score = testScalar(magnitude);
|
||||||
|
scalarMean.increment(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.info("AM DEMODULATOR WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
|
||||||
|
|
||||||
|
Mean vector64Mean = new Mean();
|
||||||
|
|
||||||
|
for(int x = 0; x < WARMUP_ITERATIONS; x++)
|
||||||
|
{
|
||||||
|
long score = testVector64(magnitude);
|
||||||
|
vector64Mean.increment(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.info("AM DEMODULATOR WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult()));
|
||||||
|
|
||||||
|
Mean vector128Mean = new Mean();
|
||||||
|
|
||||||
|
for(int x = 0; x < WARMUP_ITERATIONS; x++)
|
||||||
|
{
|
||||||
|
long score = testVector128(magnitude);
|
||||||
|
vector128Mean.increment(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.info("AM DEMODULATOR WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult()));
|
||||||
|
|
||||||
|
Mean vector256Mean = new Mean();
|
||||||
|
|
||||||
|
for(int x = 0; x < WARMUP_ITERATIONS; x++)
|
||||||
|
{
|
||||||
|
long score = testVector256(magnitude);
|
||||||
|
vector256Mean.increment(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.info("AM DEMODULATOR WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult()));
|
||||||
|
|
||||||
|
Mean vector512Mean = new Mean();
|
||||||
|
|
||||||
|
for(int x = 0; x < WARMUP_ITERATIONS; x++)
|
||||||
|
{
|
||||||
|
long score = testVector512(magnitude);
|
||||||
|
vector512Mean.increment(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.info("AM DEMODULATOR WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult()));
|
||||||
|
|
||||||
|
//Start tests
|
||||||
|
scalarMean.clear();
|
||||||
|
|
||||||
|
for(int x = 0; x < TEST_ITERATIONS; x++)
|
||||||
|
{
|
||||||
|
long score = testScalar(magnitude);
|
||||||
|
scalarMean.increment(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.info("AM DEMODULATOR - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
|
||||||
|
|
||||||
|
vector64Mean.clear();
|
||||||
|
|
||||||
|
for(int x = 0; x < TEST_ITERATIONS; x++)
|
||||||
|
{
|
||||||
|
long score = testVector64(magnitude);
|
||||||
|
vector64Mean.increment(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.info("AM DEMODULATOR - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult()));
|
||||||
|
|
||||||
|
vector128Mean.clear();
|
||||||
|
|
||||||
|
for(int x = 0; x < TEST_ITERATIONS; x++)
|
||||||
|
{
|
||||||
|
long score = testVector128(magnitude);
|
||||||
|
vector128Mean.increment(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.info("AM DEMODULATOR - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult()));
|
||||||
|
|
||||||
|
vector256Mean.clear();
|
||||||
|
|
||||||
|
for(int x = 0; x < TEST_ITERATIONS; x++)
|
||||||
|
{
|
||||||
|
long score = testVector256(magnitude);
|
||||||
|
vector256Mean.increment(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.info("AM DEMODULATOR - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult()));
|
||||||
|
|
||||||
|
vector512Mean.clear();
|
||||||
|
|
||||||
|
for(int x = 0; x < TEST_ITERATIONS; x++)
|
||||||
|
{
|
||||||
|
long score = testVector512(magnitude);
|
||||||
|
vector512Mean.increment(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.info("AM DEMODULATOR - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult()));
|
||||||
|
|
||||||
|
Implementation optimal = Implementation.SCALAR;
|
||||||
|
double optimalResult = scalarMean.getResult();
|
||||||
|
|
||||||
|
if(vector64Mean.getResult() > optimalResult)
|
||||||
|
{
|
||||||
|
optimal = Implementation.VECTOR_SIMD_64;
|
||||||
|
optimalResult = vector64Mean.getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(vector128Mean.getResult() > optimalResult)
|
||||||
|
{
|
||||||
|
optimal = Implementation.VECTOR_SIMD_128;
|
||||||
|
optimalResult = vector128Mean.getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(vector256Mean.getResult() > optimalResult)
|
||||||
|
{
|
||||||
|
optimal = Implementation.VECTOR_SIMD_256;
|
||||||
|
optimalResult = vector256Mean.getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(vector512Mean.getResult() > optimalResult)
|
||||||
|
{
|
||||||
|
optimal = Implementation.VECTOR_SIMD_512;
|
||||||
|
}
|
||||||
|
|
||||||
|
setImplementation(optimal);
|
||||||
|
|
||||||
|
mLog.info("AM DEMODULATOR - SET OPTIMAL IMPLEMENTATION TO:" + getImplementation());
|
||||||
|
}
|
||||||
|
|
||||||
|
private long testScalar(float[] magnitude)
|
||||||
|
{
|
||||||
|
|
||||||
|
double accumulator = 0.0;
|
||||||
|
long count = 0;
|
||||||
|
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
|
||||||
|
{
|
||||||
|
float[] demodulated = mScalarDemodulator.demodulateMagnitude(magnitude);
|
||||||
|
accumulator += demodulated[1];
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count + (long)(accumulator * 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long testVector64(float[] magnitude)
|
||||||
|
{
|
||||||
|
double accumulator = 0.0;
|
||||||
|
long count = 0;
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
|
||||||
|
{
|
||||||
|
float[] demodulated = mVectorDemodulator64.demodulateMagnitude(magnitude);
|
||||||
|
accumulator += demodulated[1];
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count + (long)(accumulator * 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long testVector128(float[] magnitude)
|
||||||
|
{
|
||||||
|
double accumulator = 0.0;
|
||||||
|
long count = 0;
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
|
||||||
|
{
|
||||||
|
float[] demodulated = mVectorDemodulator128.demodulateMagnitude(magnitude);
|
||||||
|
accumulator += demodulated[1];
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count + (long)(accumulator * 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long testVector256(float[] magnitude)
|
||||||
|
{
|
||||||
|
double accumulator = 0.0;
|
||||||
|
long count = 0;
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
|
||||||
|
{
|
||||||
|
float[] demodulated = mVectorAMDemodulator256.demodulateMagnitude(magnitude);
|
||||||
|
accumulator += demodulated[1];
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count + (long)(accumulator * 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long testVector512(float[] magnitude)
|
||||||
|
{
|
||||||
|
double accumulator = 0.0;
|
||||||
|
long count = 0;
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
|
||||||
|
{
|
||||||
|
float[] demodulated = mVectorAMDemodulator512.demodulateMagnitude(magnitude);
|
||||||
|
accumulator += demodulated[1];
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count + (long)(accumulator * 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
AmDemodulatorCalibration calibration = new AmDemodulatorCalibration();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
calibration.calibrate();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Error during calibration", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.am;
|
||||||
|
|
||||||
|
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
||||||
|
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||||
|
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for creating AM demodulators
|
||||||
|
*/
|
||||||
|
public class AmDemodulatorFactory
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(AmDemodulatorFactory.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the optimal AM demodulator using calibration data to select the optimal
|
||||||
|
* implementation from scalar and vector options.
|
||||||
|
* @return demodulator instance
|
||||||
|
*/
|
||||||
|
public static IAmDemodulator getAmDemodulator(float gain)
|
||||||
|
{
|
||||||
|
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.AM_DEMODULATOR);
|
||||||
|
|
||||||
|
switch(implementation)
|
||||||
|
{
|
||||||
|
case VECTOR_SIMD_64:
|
||||||
|
return new VectorAMDemodulator64(gain);
|
||||||
|
case VECTOR_SIMD_128:
|
||||||
|
return new VectorAMDemodulator128(gain);
|
||||||
|
case VECTOR_SIMD_256:
|
||||||
|
return new VectorAMDemodulator256(gain);
|
||||||
|
case VECTOR_SIMD_512:
|
||||||
|
return new VectorAMDemodulator512(gain);
|
||||||
|
case VECTOR_SIMD_PREFERRED:
|
||||||
|
return new VectorAMDemodulator64(gain);
|
||||||
|
case SCALAR:
|
||||||
|
case UNCALIBRATED:
|
||||||
|
return new ScalarAMDemodulator(gain);
|
||||||
|
default:
|
||||||
|
mLog.warn("Unrecognized optimal operation for AM demodulator: " + implementation.name());
|
||||||
|
return new ScalarAMDemodulator(gain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.am;
|
package io.github.dsheirer.dsp.am;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -6,10 +25,9 @@ package io.github.dsheirer.dsp.am;
|
|||||||
public interface IAmDemodulator
|
public interface IAmDemodulator
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Demodulate the complex I & Q sample arrays
|
* Demodulates an array of vector squared magnitudes.
|
||||||
* @param i samples
|
* @param magnitude values that are squared.
|
||||||
* @param q samples
|
* @return demodulated samples
|
||||||
* @return demodulated samples.
|
|
||||||
*/
|
*/
|
||||||
float[] demodulate(float[] i, float[] q);
|
float[] demodulateMagnitude(float[] magnitude);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -23,7 +23,7 @@ import org.apache.commons.math3.util.FastMath;
|
|||||||
/**
|
/**
|
||||||
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
|
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
|
||||||
*/
|
*/
|
||||||
public class AMDemodulator implements IAmDemodulator
|
public class ScalarAMDemodulator implements IAmDemodulator
|
||||||
{
|
{
|
||||||
private float mGain;
|
private float mGain;
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ public class AMDemodulator implements IAmDemodulator
|
|||||||
*
|
*
|
||||||
* @param gain to apply to demodulated output samples.
|
* @param gain to apply to demodulated output samples.
|
||||||
*/
|
*/
|
||||||
public AMDemodulator(float gain)
|
public ScalarAMDemodulator(float gain)
|
||||||
{
|
{
|
||||||
mGain = gain;
|
mGain = gain;
|
||||||
}
|
}
|
||||||
@@ -57,13 +57,13 @@ public class AMDemodulator implements IAmDemodulator
|
|||||||
return (float) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
|
return (float) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public float[] demodulate(float[] i, float[] q)
|
@Override
|
||||||
|
public float[] demodulateMagnitude(float[] magnitude)
|
||||||
{
|
{
|
||||||
float[] demodulated = new float[i.length];
|
float[] demodulated = new float[magnitude.length];
|
||||||
|
for(int x = 0; x < magnitude.length; x++)
|
||||||
for(int x = 0; x < i.length; x++)
|
|
||||||
{
|
{
|
||||||
demodulated[x] = (float)FastMath.sqrt((i[x] * i[x]) + (q[x] * q[x])) * mGain;
|
demodulated[x] = (float)FastMath.sqrt(magnitude[x]) * mGain;
|
||||||
}
|
}
|
||||||
|
|
||||||
return demodulated;
|
return demodulated;
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.am;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.fm.ISquelchingDemodulator;
|
||||||
|
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
|
||||||
|
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
|
||||||
|
import io.github.dsheirer.dsp.squelch.AdaptiveSquelch;
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AM demodulator with integrated squelch control.
|
||||||
|
*/
|
||||||
|
public class SquelchingAMDemodulator implements ISquelchingDemodulator, Listener<SourceEvent>
|
||||||
|
{
|
||||||
|
private static final float ZERO = 0.0f;
|
||||||
|
private final IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
|
||||||
|
private final IAmDemodulator mAmDemodulator;
|
||||||
|
private final AdaptiveSquelch mAdaptiveSquelch;
|
||||||
|
private boolean mSquelchChanged = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param gain to apply to the demodulated AM samples (e.g. 500.0f)
|
||||||
|
* @param alpha decay value of the single pole IIR filter in range: 0.0 - 1.0. The smaller the alpha value,
|
||||||
|
* the slower the squelch response.
|
||||||
|
* @param squelchThreshold in decibels. Signal power must exceed this threshold value for unsquelch.
|
||||||
|
* @param squelchAutoTrack to enable the squelch noise floor auto tracking feature.
|
||||||
|
*/
|
||||||
|
public SquelchingAMDemodulator(float gain, float alpha, float squelchThreshold, boolean squelchAutoTrack)
|
||||||
|
{
|
||||||
|
mAmDemodulator = AmDemodulatorFactory.getAmDemodulator(gain);
|
||||||
|
mAdaptiveSquelch = new AdaptiveSquelch(alpha, squelchThreshold, squelchAutoTrack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or update the sample rate for the squelch to adjust the power level notification rate.
|
||||||
|
* @param sampleRate in hertz
|
||||||
|
*/
|
||||||
|
public void setSampleRate(int sampleRate)
|
||||||
|
{
|
||||||
|
mAdaptiveSquelch.setSampleRate(sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive notifications of squelch change events from the power squelch.
|
||||||
|
*/
|
||||||
|
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||||
|
{
|
||||||
|
mAdaptiveSquelch.setSourceEventListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demodulates the complex sample arrays.
|
||||||
|
* @param i inphase sample array
|
||||||
|
* @param q quadrature sample array
|
||||||
|
* @return demodulated AM samples with gain applied.
|
||||||
|
*/
|
||||||
|
public float[] demodulate(float[] i, float[] q)
|
||||||
|
{
|
||||||
|
mAdaptiveSquelch.setSquelchChanged(false);
|
||||||
|
setSquelchChanged(false);
|
||||||
|
|
||||||
|
float[] magnitude = mMagnitudeCalculator.calculate(i, q);
|
||||||
|
float[] demodulated = mAmDemodulator.demodulateMagnitude(magnitude);
|
||||||
|
|
||||||
|
for(int x = 0; x < i.length; x++)
|
||||||
|
{
|
||||||
|
mAdaptiveSquelch.process(magnitude[x]);
|
||||||
|
|
||||||
|
if(!(mAdaptiveSquelch.isUnmuted()))
|
||||||
|
{
|
||||||
|
demodulated[x] = ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mAdaptiveSquelch.isSquelchChanged())
|
||||||
|
{
|
||||||
|
setSquelchChanged(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return demodulated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the threshold for squelch control
|
||||||
|
* @param threshold (dB)
|
||||||
|
*/
|
||||||
|
public void setSquelchThreshold(float threshold)
|
||||||
|
{
|
||||||
|
mAdaptiveSquelch.setSquelchThreshold(threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSquelchAutoTrack(boolean autoTrack)
|
||||||
|
{
|
||||||
|
mAdaptiveSquelch.setSquelchAutoTrack(autoTrack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the squelch state has changed during the processing of buffer(s)
|
||||||
|
*/
|
||||||
|
public boolean isSquelchChanged()
|
||||||
|
{
|
||||||
|
return mSquelchChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets or resets the squelch changed flag.
|
||||||
|
*/
|
||||||
|
private void setSquelchChanged(boolean changed)
|
||||||
|
{
|
||||||
|
mSquelchChanged = changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the squelch state is currently muted
|
||||||
|
*/
|
||||||
|
public boolean isMuted()
|
||||||
|
{
|
||||||
|
return mAdaptiveSquelch.isMuted();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process source events initiated by the timer and end-user.
|
||||||
|
* @param sourceEvent to process.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void receive(SourceEvent sourceEvent)
|
||||||
|
{
|
||||||
|
switch(sourceEvent.getEvent())
|
||||||
|
{
|
||||||
|
//Only forward squelch threshold & auto-track request events
|
||||||
|
case REQUEST_CURRENT_SQUELCH_THRESHOLD:
|
||||||
|
case REQUEST_CHANGE_SQUELCH_THRESHOLD:
|
||||||
|
case REQUEST_CURRENT_SQUELCH_AUTO_TRACK:
|
||||||
|
case REQUEST_CHANGE_SQUELCH_AUTO_TRACK:
|
||||||
|
{
|
||||||
|
mAdaptiveSquelch.receive(sourceEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.am;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
|
||||||
|
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
|
||||||
|
import io.github.dsheirer.vector.VectorUtilities;
|
||||||
|
import jdk.incubator.vector.FloatVector;
|
||||||
|
import jdk.incubator.vector.VectorSpecies;
|
||||||
|
import org.apache.commons.math3.util.FastMath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
|
||||||
|
*/
|
||||||
|
public class VectorAMDemodulator128 implements IAmDemodulator
|
||||||
|
{
|
||||||
|
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||||
|
private float mGain;
|
||||||
|
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs this demodulator where the specified gain is applied to demodulated output samples.
|
||||||
|
*
|
||||||
|
* @param gain to apply to demodulated output samples.
|
||||||
|
*/
|
||||||
|
public VectorAMDemodulator128(float gain)
|
||||||
|
{
|
||||||
|
mGain = gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the gain to the specified level.
|
||||||
|
*/
|
||||||
|
public void setGain(float gain)
|
||||||
|
{
|
||||||
|
mGain = gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demodulates the comples I/Q sample and returns the demodulated output sample.
|
||||||
|
*
|
||||||
|
* @param inphase sample
|
||||||
|
* @param quadrature sample
|
||||||
|
* @return AM demodulated sample
|
||||||
|
*/
|
||||||
|
public float demodulate(float inphase, float quadrature)
|
||||||
|
{
|
||||||
|
return (float) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] demodulateMagnitude(float[] magnitude)
|
||||||
|
{
|
||||||
|
VectorUtilities.checkArrayLength(magnitude, VECTOR_SPECIES);
|
||||||
|
FloatVector gain = FloatVector.broadcast(VECTOR_SPECIES, mGain);
|
||||||
|
float[] demodulated = new float[magnitude.length];
|
||||||
|
|
||||||
|
FloatVector vector;
|
||||||
|
for(int x = 0; x < magnitude.length; x += VECTOR_SPECIES.length())
|
||||||
|
{
|
||||||
|
vector = FloatVector.fromArray(VECTOR_SPECIES, magnitude, x);
|
||||||
|
vector.sqrt().mul(gain).intoArray(demodulated, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return demodulated;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.am;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
|
||||||
|
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
|
||||||
|
import io.github.dsheirer.vector.VectorUtilities;
|
||||||
|
import jdk.incubator.vector.FloatVector;
|
||||||
|
import jdk.incubator.vector.VectorSpecies;
|
||||||
|
import org.apache.commons.math3.util.FastMath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
|
||||||
|
*/
|
||||||
|
public class VectorAMDemodulator256 implements IAmDemodulator
|
||||||
|
{
|
||||||
|
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||||
|
private float mGain;
|
||||||
|
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs this demodulator where the specified gain is applied to demodulated output samples.
|
||||||
|
*
|
||||||
|
* @param gain to apply to demodulated output samples.
|
||||||
|
*/
|
||||||
|
public VectorAMDemodulator256(float gain)
|
||||||
|
{
|
||||||
|
mGain = gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the gain to the specified level.
|
||||||
|
*/
|
||||||
|
public void setGain(float gain)
|
||||||
|
{
|
||||||
|
mGain = gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demodulates the comples I/Q sample and returns the demodulated output sample.
|
||||||
|
*
|
||||||
|
* @param inphase sample
|
||||||
|
* @param quadrature sample
|
||||||
|
* @return AM demodulated sample
|
||||||
|
*/
|
||||||
|
public float demodulate(float inphase, float quadrature)
|
||||||
|
{
|
||||||
|
return (float) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] demodulateMagnitude(float[] magnitude)
|
||||||
|
{
|
||||||
|
VectorUtilities.checkArrayLength(magnitude, VECTOR_SPECIES);
|
||||||
|
FloatVector gain = FloatVector.broadcast(VECTOR_SPECIES, mGain);
|
||||||
|
float[] demodulated = new float[magnitude.length];
|
||||||
|
|
||||||
|
FloatVector vector;
|
||||||
|
for(int x = 0; x < magnitude.length; x += VECTOR_SPECIES.length())
|
||||||
|
{
|
||||||
|
vector = FloatVector.fromArray(VECTOR_SPECIES, magnitude, x);
|
||||||
|
vector.sqrt().mul(gain).intoArray(demodulated, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return demodulated;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.am;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
|
||||||
|
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
|
||||||
|
import io.github.dsheirer.vector.VectorUtilities;
|
||||||
|
import jdk.incubator.vector.FloatVector;
|
||||||
|
import jdk.incubator.vector.VectorSpecies;
|
||||||
|
import org.apache.commons.math3.util.FastMath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
|
||||||
|
*/
|
||||||
|
public class VectorAMDemodulator512 implements IAmDemodulator
|
||||||
|
{
|
||||||
|
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||||
|
private float mGain;
|
||||||
|
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs this demodulator where the specified gain is applied to demodulated output samples.
|
||||||
|
*
|
||||||
|
* @param gain to apply to demodulated output samples.
|
||||||
|
*/
|
||||||
|
public VectorAMDemodulator512(float gain)
|
||||||
|
{
|
||||||
|
mGain = gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the gain to the specified level.
|
||||||
|
*/
|
||||||
|
public void setGain(float gain)
|
||||||
|
{
|
||||||
|
mGain = gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demodulates the comples I/Q sample and returns the demodulated output sample.
|
||||||
|
*
|
||||||
|
* @param inphase sample
|
||||||
|
* @param quadrature sample
|
||||||
|
* @return AM demodulated sample
|
||||||
|
*/
|
||||||
|
public float demodulate(float inphase, float quadrature)
|
||||||
|
{
|
||||||
|
return (float) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] demodulateMagnitude(float[] magnitude)
|
||||||
|
{
|
||||||
|
VectorUtilities.checkArrayLength(magnitude, VECTOR_SPECIES);
|
||||||
|
FloatVector gain = FloatVector.broadcast(VECTOR_SPECIES, mGain);
|
||||||
|
float[] demodulated = new float[magnitude.length];
|
||||||
|
|
||||||
|
FloatVector vector;
|
||||||
|
for(int x = 0; x < magnitude.length; x += VECTOR_SPECIES.length())
|
||||||
|
{
|
||||||
|
vector = FloatVector.fromArray(VECTOR_SPECIES, magnitude, x);
|
||||||
|
vector.sqrt().mul(gain).intoArray(demodulated, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return demodulated;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.am;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
|
||||||
|
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
|
||||||
|
import io.github.dsheirer.vector.VectorUtilities;
|
||||||
|
import jdk.incubator.vector.FloatVector;
|
||||||
|
import jdk.incubator.vector.VectorSpecies;
|
||||||
|
import org.apache.commons.math3.util.FastMath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
|
||||||
|
*/
|
||||||
|
public class VectorAMDemodulator64 implements IAmDemodulator
|
||||||
|
{
|
||||||
|
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||||
|
private float mGain;
|
||||||
|
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs this demodulator where the specified gain is applied to demodulated output samples.
|
||||||
|
*
|
||||||
|
* @param gain to apply to demodulated output samples.
|
||||||
|
*/
|
||||||
|
public VectorAMDemodulator64(float gain)
|
||||||
|
{
|
||||||
|
mGain = gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the gain to the specified level.
|
||||||
|
*/
|
||||||
|
public void setGain(float gain)
|
||||||
|
{
|
||||||
|
mGain = gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demodulates the comples I/Q sample and returns the demodulated output sample.
|
||||||
|
*
|
||||||
|
* @param inphase sample
|
||||||
|
* @param quadrature sample
|
||||||
|
* @return AM demodulated sample
|
||||||
|
*/
|
||||||
|
public float demodulate(float inphase, float quadrature)
|
||||||
|
{
|
||||||
|
return (float) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] demodulateMagnitude(float[] magnitude)
|
||||||
|
{
|
||||||
|
VectorUtilities.checkArrayLength(magnitude, VECTOR_SPECIES);
|
||||||
|
FloatVector gain = FloatVector.broadcast(VECTOR_SPECIES, mGain);
|
||||||
|
float[] demodulated = new float[magnitude.length];
|
||||||
|
|
||||||
|
FloatVector vector;
|
||||||
|
for(int x = 0; x < magnitude.length; x += VECTOR_SPECIES.length())
|
||||||
|
{
|
||||||
|
vector = FloatVector.fromArray(VECTOR_SPECIES, magnitude, x);
|
||||||
|
vector.sqrt().mul(gain).intoArray(demodulated, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return demodulated;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,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 to process 1/20th batch of 152 buffers a second, at 40 times per second
|
//Sized to process 40 times per second
|
||||||
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(152 / 20, 25);
|
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(25);
|
||||||
private FloatFFT_1D mFFT;
|
private FloatFFT_1D mFFT;
|
||||||
private float[] mInlineSamples;
|
private float[] mInlineSamples;
|
||||||
private float[] mInlineFilter;
|
private float[] mInlineFilter;
|
||||||
@@ -406,9 +406,9 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
|||||||
*/
|
*/
|
||||||
public class IFFTProcessorDispatcher extends Dispatcher<List<float[]>>
|
public class IFFTProcessorDispatcher extends Dispatcher<List<float[]>>
|
||||||
{
|
{
|
||||||
public IFFTProcessorDispatcher(int batchSize, long interval)
|
public IFFTProcessorDispatcher(long interval)
|
||||||
{
|
{
|
||||||
super("sdrtrunk polyphase ifft processor", batchSize, interval);
|
super("sdrtrunk polyphase ifft processor", 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
|
||||||
@@ -421,9 +421,12 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
|||||||
|
|
||||||
for(float[] channelResults: list)
|
for(float[] channelResults: list)
|
||||||
{
|
{
|
||||||
//Rotate each of the channels to the correct phase using the IFFT
|
if(channelResults != null)
|
||||||
mFFT.complexInverse(channelResults, true);
|
{
|
||||||
processedChannelResults.add(channelResults);
|
//Rotate each of the channels to the correct phase using the IFFT
|
||||||
|
mFFT.complexInverse(channelResults, true);
|
||||||
|
processedChannelResults.add(channelResults);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(processedChannelResults);
|
dispatch(processedChannelResults);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import io.github.dsheirer.buffer.INativeBufferProvider;
|
|||||||
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;
|
||||||
|
import io.github.dsheirer.log.LoggingSuppressor;
|
||||||
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.sample.complex.InterleavedComplexSamples;
|
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||||
@@ -65,6 +66,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 static final DecimalFormat FREQUENCY_FORMAT = new DecimalFormat("0.00000");
|
||||||
|
private static final LoggingSuppressor LOGGING_SUPPRESSOR = new LoggingSuppressor(LoggerFactory.getLogger(PolyphaseChannelManager.class));
|
||||||
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,7 +110,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("sdrtrunk polyphase buffer processor", 50, 10);
|
mBufferDispatcher = new Dispatcher("sdrtrunk polyphase buffer processor", 10);
|
||||||
mBufferDispatcher.setListener(mNativeBufferReceiver);
|
mBufferDispatcher.setListener(mNativeBufferReceiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +205,8 @@ public class PolyphaseChannelManager implements ISourceEventProcessor
|
|||||||
}
|
}
|
||||||
catch(IllegalArgumentException iae)
|
catch(IllegalArgumentException iae)
|
||||||
{
|
{
|
||||||
mLog.debug("Couldn't design final output low pass filter for polyphase channel source");
|
LOGGING_SUPPRESSOR.error(iae.getMessage(), 3, "Couldn't allocate channel. " + iae.getMessage());
|
||||||
|
channelSource = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ public abstract class ChannelOutputProcessor implements IPolyphaseChannelOutputP
|
|||||||
mInputChannelCount = inputChannelCount;
|
mInputChannelCount = inputChannelCount;
|
||||||
//Process 1/10th of the sample rate per second at a rate of 20 times a second (200% of anticipated rate)
|
//Process 1/10th of the sample rate per second at a rate of 20 times a second (200% of anticipated rate)
|
||||||
mHeartbeatManager = heartbeatManager;
|
mHeartbeatManager = heartbeatManager;
|
||||||
mChannelResultsDispatcher = new Dispatcher("sdrtrunk polyphase channel", (int)(sampleRate / 10),
|
mChannelResultsDispatcher = new Dispatcher("sdrtrunk polyphase channel",50, mHeartbeatManager);
|
||||||
50, mHeartbeatManager);
|
|
||||||
mChannelResultsDispatcher.setListener(floats -> {
|
mChannelResultsDispatcher.setListener(floats -> {
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter.iir;
|
package io.github.dsheirer.dsp.filter.iir;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,7 +59,7 @@ public class SinglePoleIirFilter
|
|||||||
* @param sample to process/filter
|
* @param sample to process/filter
|
||||||
* @return filtered output
|
* @return filtered output
|
||||||
*/
|
*/
|
||||||
public double filter(float sample)
|
public float filter(float sample)
|
||||||
{
|
{
|
||||||
mOutput = (mOutput * mOneMinusAlpha) + (mAlpha * sample);
|
mOutput = (mOutput * mOneMinusAlpha) + (mAlpha * sample);
|
||||||
return mOutput;
|
return mOutput;
|
||||||
@@ -49,7 +68,7 @@ public class SinglePoleIirFilter
|
|||||||
/**
|
/**
|
||||||
* Current output value
|
* Current output value
|
||||||
*/
|
*/
|
||||||
public double getValue()
|
public float getValue()
|
||||||
{
|
{
|
||||||
return mOutput;
|
return mOutput;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -37,40 +37,23 @@ public class FmDemodulatorFactory
|
|||||||
* implementation from scalar and vector options.
|
* implementation from scalar and vector options.
|
||||||
* @return demodulator instance
|
* @return demodulator instance
|
||||||
*/
|
*/
|
||||||
public static IFmDemodulator getFmDemodulator()
|
public static IDemodulator getFmDemodulator()
|
||||||
{
|
{
|
||||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.FM_DEMODULATOR);
|
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.FM_DEMODULATOR);
|
||||||
|
|
||||||
switch(implementation)
|
switch(implementation)
|
||||||
{
|
{
|
||||||
case VECTOR_SIMD_PREFERRED:
|
case VECTOR_SIMD_64:
|
||||||
return new VectorFMDemodulator();
|
return new VectorFMDemodulator64();
|
||||||
|
case VECTOR_SIMD_128:
|
||||||
|
return new VectorFMDemodulator128();
|
||||||
|
case VECTOR_SIMD_256:
|
||||||
|
return new VectorFMDemodulator256();
|
||||||
|
case VECTOR_SIMD_512:
|
||||||
|
return new VectorFMDemodulator512();
|
||||||
case SCALAR:
|
case SCALAR:
|
||||||
return new ScalarFMDemodulator();
|
|
||||||
default:
|
default:
|
||||||
mLog.warn("Unrecognized optimal operation for FM demodulator: " + implementation.name());
|
|
||||||
return new ScalarFMDemodulator();
|
return new ScalarFMDemodulator();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the optimal Squelching FM demodulator using calibration data to select the optimal
|
|
||||||
* implementation from scalar and vector options.
|
|
||||||
* @return demodulator instance
|
|
||||||
*/
|
|
||||||
public static ISquelchingFmDemodulator getSquelchingFmDemodulator(float alpha, float threshold, int ramp)
|
|
||||||
{
|
|
||||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.FM_DEMODULATOR);
|
|
||||||
|
|
||||||
switch(implementation)
|
|
||||||
{
|
|
||||||
case VECTOR_SIMD_PREFERRED:
|
|
||||||
return new VectorSquelchingFMDemodulator(alpha, threshold, ramp);
|
|
||||||
case SCALAR:
|
|
||||||
return new ScalarSquelchingFMDemodulator(alpha, threshold, ramp);
|
|
||||||
default:
|
|
||||||
mLog.warn("Unrecognized optimal operation for Squelching FM demodulator: " + implementation.name());
|
|
||||||
return new ScalarSquelchingFMDemodulator(alpha, threshold, ramp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/main/java/io/github/dsheirer/dsp/fm/IDemodulator.java
Normal file
34
src/main/java/io/github/dsheirer/dsp/fm/IDemodulator.java
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.fm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for demodulator of complex sample arrays.
|
||||||
|
*/
|
||||||
|
public interface IDemodulator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Demodulate the complex sample array and return an array of real audio samples.
|
||||||
|
* @param i samples to demodulate
|
||||||
|
* @param q samples to demodulate
|
||||||
|
* @return demodulated samples.
|
||||||
|
*/
|
||||||
|
float[] demodulate(float[] i, float[] q);
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package io.github.dsheirer.dsp.fm;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for FM demodulator
|
|
||||||
*/
|
|
||||||
public interface IFmDemodulator
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Demodulate the complex sample array and return an array of real audio samples.
|
|
||||||
* @param samples to demodulate
|
|
||||||
* @return demodulated samples.
|
|
||||||
*/
|
|
||||||
float[] demodulate(float[] i, float[] q);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.fm;
|
||||||
|
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for FM demodulator that provides squelch control.
|
||||||
|
*/
|
||||||
|
public interface ISquelchingDemodulator extends IDemodulator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Sets the sample rate so that the squelch can adjust the channel power level broadcast notification rate
|
||||||
|
* @param sampleRate in hertz
|
||||||
|
*/
|
||||||
|
void setSampleRate(int sampleRate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the squelch state has changed.
|
||||||
|
*/
|
||||||
|
boolean isSquelchChanged();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the squelch state is currently muted
|
||||||
|
*/
|
||||||
|
boolean isMuted();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the threshold to use for power squelch
|
||||||
|
* @param threshold in decibels
|
||||||
|
*/
|
||||||
|
void setSquelchThreshold(float threshold);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the squelch auto-tracking feature where the squelch threshold is auto-adjusted based on the
|
||||||
|
* observed channel power when the signal is not present (ie squelch state).
|
||||||
|
* @param autoTrack true to enable
|
||||||
|
*/
|
||||||
|
void setSquelchAutoTrack(boolean autoTrack);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive notifications of squelch change events from the power squelch.
|
||||||
|
*/
|
||||||
|
void setSourceEventListener(Listener<SourceEvent> listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a source event
|
||||||
|
*/
|
||||||
|
void receive(SourceEvent sourceEvent);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package io.github.dsheirer.dsp.fm;
|
|
||||||
|
|
||||||
import io.github.dsheirer.sample.Listener;
|
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for FM demodulator that provides squelch control.
|
|
||||||
*/
|
|
||||||
public interface ISquelchingFmDemodulator extends IFmDemodulator
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Indicates if the squelch state has changed.
|
|
||||||
*/
|
|
||||||
boolean isSquelchChanged();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the squelch state is currently muted
|
|
||||||
*/
|
|
||||||
boolean isMuted();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the threshold to use for power squelch
|
|
||||||
* @param threshold in decibels
|
|
||||||
*/
|
|
||||||
void setSquelchThreshold(double threshold);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the demodulator
|
|
||||||
*/
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers the listener to receive notifications of squelch change events from the power squelch.
|
|
||||||
*/
|
|
||||||
void setSourceEventListener(Listener<SourceEvent> listener);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receives a source event
|
|
||||||
*/
|
|
||||||
void receive(SourceEvent sourceEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -18,16 +18,15 @@
|
|||||||
*/
|
*/
|
||||||
package io.github.dsheirer.dsp.fm;
|
package io.github.dsheirer.dsp.fm;
|
||||||
|
|
||||||
import io.github.dsheirer.sample.complex.Complex;
|
|
||||||
import org.apache.commons.math3.util.FastMath;
|
import org.apache.commons.math3.util.FastMath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FM Demodulator for demodulating complex samples and producing demodulated floating point samples.
|
* FM Demodulator for demodulating complex samples and producing demodulated floating point samples.
|
||||||
*/
|
*/
|
||||||
public class ScalarFMDemodulator implements IFmDemodulator
|
public class ScalarFMDemodulator implements IDemodulator
|
||||||
{
|
{
|
||||||
private float mPreviousI = 0.0f;
|
protected float mPreviousI = 0.0f;
|
||||||
private float mPreviousQ = 0.0f;
|
protected float mPreviousQ = 0.0f;
|
||||||
protected float mGain;
|
protected float mGain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,50 +46,6 @@ public class ScalarFMDemodulator implements IFmDemodulator
|
|||||||
mGain = gain;
|
mGain = gain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Demodulates the I/Q sample via complex sample multiplication by the complex conjugates of the most recently
|
|
||||||
* demodulated complex sample (ie previous sample). Each new sample overwrites the previously stored sample to
|
|
||||||
* allow future invocations of the method to simply use a new sample value.
|
|
||||||
*
|
|
||||||
* @param currentI of the sample
|
|
||||||
* @param currentQ of the sample
|
|
||||||
* @return demodulated sample
|
|
||||||
*/
|
|
||||||
public float demodulate(float currentI, float currentQ)
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Multiply the current sample against the complex conjugate of the
|
|
||||||
* previous sample to derive the phase delta between the two samples
|
|
||||||
*
|
|
||||||
* Negating the previous sample quadrature produces the conjugate
|
|
||||||
*/
|
|
||||||
double inphase = (currentI * mPreviousI) - (currentQ * -mPreviousQ);
|
|
||||||
double quadrature = (currentQ * mPreviousI) + (currentI * -mPreviousQ);
|
|
||||||
|
|
||||||
double angle = 0.0f;
|
|
||||||
|
|
||||||
//Check for divide by zero
|
|
||||||
if(inphase != 0)
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Use the arc-tangent of quadrature divided by inphase to
|
|
||||||
* get the phase angle (+/-) which was directly manipulated by the
|
|
||||||
* original message waveform during the modulation. This value now
|
|
||||||
* serves as the instantaneous amplitude of the demodulated signal
|
|
||||||
*/
|
|
||||||
double denominator = 1.0d / inphase;
|
|
||||||
angle = FastMath.atan((double)quadrature * denominator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store the current sample to use during the next iteration
|
|
||||||
*/
|
|
||||||
mPreviousI = currentI;
|
|
||||||
mPreviousQ = currentQ;
|
|
||||||
|
|
||||||
return (float)(angle * mGain);
|
|
||||||
}
|
|
||||||
|
|
||||||
public float[] demodulate(float[] i, float[] q)
|
public float[] demodulate(float[] i, float[] q)
|
||||||
{
|
{
|
||||||
float[] demodulated = new float[i.length];
|
float[] demodulated = new float[i.length];
|
||||||
@@ -135,35 +90,6 @@ public class ScalarFMDemodulator implements IFmDemodulator
|
|||||||
return demodulated;
|
return demodulated;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Demodulates the complex samples and returns the demodulated value.
|
|
||||||
* @param previous
|
|
||||||
* @param current
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static double demodulate(Complex previous, Complex current)
|
|
||||||
{
|
|
||||||
double inphase = (current.inphase() * previous.inphase()) - (current.quadrature() * -previous.quadrature());
|
|
||||||
double quadrature = (current.quadrature() * previous.inphase()) + (current.inphase() * -previous.quadrature());
|
|
||||||
|
|
||||||
double angle = 0.0f;
|
|
||||||
|
|
||||||
//Check for divide by zero
|
|
||||||
if(inphase != 0)
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Use the arc-tangent of quadrature divided by inphase to
|
|
||||||
* get the phase angle (+/-) which was directly manipulated by the
|
|
||||||
* original message waveform during the modulation. This value now
|
|
||||||
* serves as the instantaneous amplitude of the demodulated signal
|
|
||||||
*/
|
|
||||||
double denominator = 1.0d / inphase;
|
|
||||||
angle = FastMath.atan((double)quadrature * denominator);
|
|
||||||
}
|
|
||||||
|
|
||||||
return angle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
//no-op
|
//no-op
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
package io.github.dsheirer.dsp.fm;
|
|
||||||
|
|
||||||
import io.github.dsheirer.dsp.squelch.PowerSquelch;
|
|
||||||
import io.github.dsheirer.sample.Listener;
|
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FM Demodulator for demodulating complex samples and producing demodulated floating point samples.
|
|
||||||
*
|
|
||||||
* Implements listener of source events to process runtime squelch threshold change request events
|
|
||||||
* which are forwarded to the power squelch control.
|
|
||||||
*/
|
|
||||||
public class ScalarSquelchingFMDemodulator extends ScalarFMDemodulator implements ISquelchingFmDemodulator, Listener<SourceEvent>
|
|
||||||
{
|
|
||||||
private PowerSquelch mPowerSquelch;
|
|
||||||
private boolean mSquelchChanged = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an FM demodulator instance with a default gain of 1.0.
|
|
||||||
*/
|
|
||||||
public ScalarSquelchingFMDemodulator(float alpha, float threshold, int ramp)
|
|
||||||
{
|
|
||||||
mPowerSquelch = new PowerSquelch(alpha, threshold, ramp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers the listener to receive notifications of squelch change events from the power squelch.
|
|
||||||
*/
|
|
||||||
public void setSourceEventListener(Listener<SourceEvent> listener)
|
|
||||||
{
|
|
||||||
mPowerSquelch.setSourceEventListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the threshold for squelch control
|
|
||||||
* @param threshold (dB)
|
|
||||||
*/
|
|
||||||
public void setSquelchThreshold(double threshold)
|
|
||||||
{
|
|
||||||
mPowerSquelch.setSquelchThreshold(threshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the squelch state has changed during the processing of buffer(s)
|
|
||||||
*/
|
|
||||||
public boolean isSquelchChanged()
|
|
||||||
{
|
|
||||||
return mSquelchChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets or resets the squelch changed flag.
|
|
||||||
*/
|
|
||||||
private void setSquelchChanged(boolean changed)
|
|
||||||
{
|
|
||||||
mSquelchChanged = changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the squelch state is currently muted
|
|
||||||
*/
|
|
||||||
public boolean isMuted()
|
|
||||||
{
|
|
||||||
return mPowerSquelch.isMuted();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receive(SourceEvent sourceEvent)
|
|
||||||
{
|
|
||||||
//Only forward squelch threshold change request events
|
|
||||||
if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD)
|
|
||||||
{
|
|
||||||
mPowerSquelch.receive(sourceEvent);
|
|
||||||
}
|
|
||||||
else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD)
|
|
||||||
{
|
|
||||||
mPowerSquelch.receive(sourceEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.fm;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
|
||||||
|
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
|
||||||
|
import io.github.dsheirer.dsp.squelch.AdaptiveSquelch;
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FM Demodulator for demodulating complex samples and producing demodulated floating point samples.
|
||||||
|
*
|
||||||
|
* Implements listener of source events to process runtime squelch threshold change request events
|
||||||
|
* which are forwarded to the power squelch control.
|
||||||
|
*/
|
||||||
|
public class SquelchingFMDemodulator implements ISquelchingDemodulator, Listener<SourceEvent>
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(SquelchingFMDemodulator.class);
|
||||||
|
private static final float ZERO = 0.0f;
|
||||||
|
private final AdaptiveSquelch mAdaptiveSquelch;
|
||||||
|
private final IMagnitudeCalculator mMagnitude = MagnitudeFactory.getMagnitudeCalculator();
|
||||||
|
private final IDemodulator mFmDemodulator = FmDemodulatorFactory.getFmDemodulator();
|
||||||
|
private boolean mSquelchChanged = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an FM demodulator instance with a default gain of 1.0.
|
||||||
|
*
|
||||||
|
* @param alpha decay value of the single pole IIR filter in range: 0.0 - 1.0. The smaller the alpha value,
|
||||||
|
* the slower the squelch response.
|
||||||
|
* @param squelchThreshold in decibels. Signal power must exceed this threshold value for unsquelch.
|
||||||
|
* causes immediate mute and unmute. Set to higher count to prevent mute/unmute flapping.
|
||||||
|
* @param squelchAutoTrack to enable the squelch noise floor auto-tracking feature.
|
||||||
|
*/
|
||||||
|
public SquelchingFMDemodulator(float alpha, float squelchThreshold, boolean squelchAutoTrack)
|
||||||
|
{
|
||||||
|
mAdaptiveSquelch = new AdaptiveSquelch(alpha, squelchThreshold, squelchAutoTrack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set or update the sample rate for the squelch to adjust the power level notification rate.
|
||||||
|
* @param sampleRate in hertz
|
||||||
|
*/
|
||||||
|
public void setSampleRate(int sampleRate)
|
||||||
|
{
|
||||||
|
mAdaptiveSquelch.setSampleRate(sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive notifications of squelch change events from the power squelch.
|
||||||
|
*/
|
||||||
|
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||||
|
{
|
||||||
|
mAdaptiveSquelch.setSourceEventListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demodulates the complex (I/Q) sample arrays
|
||||||
|
* @param i inphase samples
|
||||||
|
* @param q quadrature samples
|
||||||
|
* @return demodulated real samples
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public float[] demodulate(float[] i, float[] q)
|
||||||
|
{
|
||||||
|
setSquelchChanged(false);
|
||||||
|
|
||||||
|
float[] demodulated = mFmDemodulator.demodulate(i, q);
|
||||||
|
float[] magnitude = mMagnitude.calculate(i, q);
|
||||||
|
|
||||||
|
for(int x = 0; x < magnitude.length; x++)
|
||||||
|
{
|
||||||
|
mAdaptiveSquelch.process(magnitude[x]);
|
||||||
|
|
||||||
|
if(!(mAdaptiveSquelch.isUnmuted()))
|
||||||
|
{
|
||||||
|
demodulated[x] = ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mAdaptiveSquelch.isSquelchChanged())
|
||||||
|
{
|
||||||
|
setSquelchChanged(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return demodulated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the threshold for squelch control
|
||||||
|
* @param threshold (dB)
|
||||||
|
*/
|
||||||
|
public void setSquelchThreshold(float threshold)
|
||||||
|
{
|
||||||
|
mAdaptiveSquelch.setSquelchThreshold(threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSquelchAutoTrack(boolean autoTrack)
|
||||||
|
{
|
||||||
|
mAdaptiveSquelch.setSquelchAutoTrack(autoTrack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the squelch state has changed during the processing of buffer(s)
|
||||||
|
*/
|
||||||
|
public boolean isSquelchChanged()
|
||||||
|
{
|
||||||
|
return mSquelchChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets or resets the squelch changed flag.
|
||||||
|
*/
|
||||||
|
private void setSquelchChanged(boolean changed)
|
||||||
|
{
|
||||||
|
mSquelchChanged = changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the squelch state is currently muted
|
||||||
|
*/
|
||||||
|
public boolean isMuted()
|
||||||
|
{
|
||||||
|
return mAdaptiveSquelch.isMuted();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(SourceEvent sourceEvent)
|
||||||
|
{
|
||||||
|
switch(sourceEvent.getEvent())
|
||||||
|
{
|
||||||
|
//Only forward squelch threshold and auto-track current & change requests
|
||||||
|
case REQUEST_CURRENT_SQUELCH_THRESHOLD:
|
||||||
|
case REQUEST_CHANGE_SQUELCH_THRESHOLD:
|
||||||
|
case REQUEST_CURRENT_SQUELCH_AUTO_TRACK:
|
||||||
|
case REQUEST_CHANGE_SQUELCH_AUTO_TRACK:
|
||||||
|
mAdaptiveSquelch.receive(sourceEvent);
|
||||||
|
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
|
||||||
@@ -26,17 +26,12 @@ import jdk.incubator.vector.VectorSpecies;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FM demodulator that uses JDK 17+ SIMD vector intrinsics
|
* FM demodulator that uses JDK 17+ SIMD vector intrinsics
|
||||||
*/
|
*/
|
||||||
public class VectorFMDemodulator implements IFmDemodulator
|
public abstract class VectorFMDemodulator implements IDemodulator
|
||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(VectorFMDemodulator.class);
|
private static final Logger mLog = LoggerFactory.getLogger(VectorFMDemodulator.class);
|
||||||
protected static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED;
|
|
||||||
protected static final float ZERO = 0.0f;
|
protected static final float ZERO = 0.0f;
|
||||||
private static final int BUFFER_OVERLAP = 1;
|
private static final int BUFFER_OVERLAP = 1;
|
||||||
|
|
||||||
@@ -48,9 +43,16 @@ public class VectorFMDemodulator implements IFmDemodulator
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vector species as specified by the implementing sub-class.
|
||||||
|
*/
|
||||||
|
protected abstract VectorSpecies<Float> getVectorSpecies();
|
||||||
|
|
||||||
@Override public float[] demodulate(float[] i, float[] q)
|
@Override public float[] demodulate(float[] i, float[] q)
|
||||||
{
|
{
|
||||||
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
// VectorSpecies<Float> species= getVectorSpecies();
|
||||||
|
|
||||||
|
VectorUtilities.checkComplexArrayLength(i, q, getVectorSpecies());
|
||||||
|
|
||||||
if(mIBuffer.length != (i.length + BUFFER_OVERLAP))
|
if(mIBuffer.length != (i.length + BUFFER_OVERLAP))
|
||||||
{
|
{
|
||||||
@@ -70,12 +72,12 @@ public class VectorFMDemodulator implements IFmDemodulator
|
|||||||
|
|
||||||
FloatVector currentI, currentQ, previousI, previousQ, demod, demodI, demodQ;
|
FloatVector currentI, currentQ, previousI, previousQ, demod, demodI, demodQ;
|
||||||
|
|
||||||
for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += VECTOR_SPECIES.length())
|
for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += getVectorSpecies().length())
|
||||||
{
|
{
|
||||||
previousI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer);
|
previousI = FloatVector.fromArray(getVectorSpecies(), mIBuffer, bufferPointer);
|
||||||
previousQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer);
|
previousQ = FloatVector.fromArray(getVectorSpecies(), mQBuffer, bufferPointer);
|
||||||
currentI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer + 1);
|
currentI = FloatVector.fromArray(getVectorSpecies(), mIBuffer, bufferPointer + 1);
|
||||||
currentQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer + 1);
|
currentQ = FloatVector.fromArray(getVectorSpecies(), mQBuffer, bufferPointer + 1);
|
||||||
|
|
||||||
demodI = currentI.mul(previousI).sub(currentQ.mul(previousQ.neg()));
|
demodI = currentI.mul(previousI).sub(currentQ.mul(previousQ.neg()));
|
||||||
demodQ = currentQ.mul(previousI).add(currentI.mul(previousQ.neg()));
|
demodQ = currentQ.mul(previousI).add(currentI.mul(previousQ.neg()));
|
||||||
@@ -89,53 +91,4 @@ public class VectorFMDemodulator implements IFmDemodulator
|
|||||||
|
|
||||||
return demodulated;
|
return demodulated;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
int iterations = 3_000_000;
|
|
||||||
int sampleSize = 2048;
|
|
||||||
|
|
||||||
Random random = new Random();
|
|
||||||
|
|
||||||
float[] iSamples = new float[sampleSize];
|
|
||||||
float[] qSamples = new float[sampleSize];
|
|
||||||
for(int x = 0; x < iSamples.length; x++)
|
|
||||||
{
|
|
||||||
iSamples[x] = random.nextFloat() * 2.0f - 1.0f;
|
|
||||||
qSamples[x] = random.nextFloat() * 2.0f - 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean validation = false;
|
|
||||||
|
|
||||||
ScalarFMDemodulator legacy = new ScalarFMDemodulator();
|
|
||||||
VectorFMDemodulator vector = new VectorFMDemodulator();
|
|
||||||
|
|
||||||
if(validation)
|
|
||||||
{
|
|
||||||
float[] legacySamples = legacy.demodulate(iSamples, qSamples);
|
|
||||||
float[] vectorSamples = vector.demodulate(iSamples, qSamples);
|
|
||||||
System.out.println("LEGACY:" + Arrays.toString(legacySamples));
|
|
||||||
System.out.println("VECTOR:" + Arrays.toString(vectorSamples));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
System.out.println("Test Starting ...");
|
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
|
|
||||||
double accumulator = 0.0;
|
|
||||||
|
|
||||||
for(int i = 0; i < iterations; i++)
|
|
||||||
{
|
|
||||||
float[] samples = legacy.demodulate(iSamples, qSamples);
|
|
||||||
// float[] samples = vector.demodulate(iSamples, qSamples);
|
|
||||||
accumulator += samples[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
double elapsed = System.currentTimeMillis() - start;
|
|
||||||
|
|
||||||
DecimalFormat df = new DecimalFormat("0.000");
|
|
||||||
System.out.println("Accumulator: " + accumulator);
|
|
||||||
System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.fm;
|
||||||
|
|
||||||
|
import io.github.dsheirer.vector.VectorUtilities;
|
||||||
|
import jdk.incubator.vector.FloatVector;
|
||||||
|
import jdk.incubator.vector.VectorOperators;
|
||||||
|
import jdk.incubator.vector.VectorSpecies;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FM demodulator that uses JDK 17+ SIMD vector intrinsics
|
||||||
|
*/
|
||||||
|
public class VectorFMDemodulator128 implements IDemodulator
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(VectorFMDemodulator128.class);
|
||||||
|
protected static final float ZERO = 0.0f;
|
||||||
|
private static final int BUFFER_OVERLAP = 1;
|
||||||
|
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||||
|
|
||||||
|
//Initialize buffers to be non-null and resize once the first buffer arrives
|
||||||
|
private float[] mIBuffer = new float[1];
|
||||||
|
private float[] mQBuffer = new float[1];
|
||||||
|
|
||||||
|
public VectorFMDemodulator128()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public float[] demodulate(float[] i, float[] q)
|
||||||
|
{
|
||||||
|
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||||
|
|
||||||
|
if(mIBuffer.length != (i.length + BUFFER_OVERLAP))
|
||||||
|
{
|
||||||
|
mIBuffer = new float[i.length + BUFFER_OVERLAP];
|
||||||
|
mQBuffer = new float[q.length + BUFFER_OVERLAP];
|
||||||
|
}
|
||||||
|
|
||||||
|
//Copy last sample to beginning of buffer arrays
|
||||||
|
mIBuffer[0] = mIBuffer[mIBuffer.length - 1];
|
||||||
|
mQBuffer[0] = mQBuffer[mQBuffer.length - 1];
|
||||||
|
|
||||||
|
//Copy new samples to end of buffer arrays
|
||||||
|
System.arraycopy(i, 0, mIBuffer, 1, i.length);
|
||||||
|
System.arraycopy(q, 0, mQBuffer, 1, q.length);
|
||||||
|
|
||||||
|
float[] demodulated = new float[i.length];
|
||||||
|
|
||||||
|
FloatVector currentI, currentQ, previousI, previousQ, demod, demodI, demodQ;
|
||||||
|
|
||||||
|
for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += VECTOR_SPECIES.length())
|
||||||
|
{
|
||||||
|
previousI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer);
|
||||||
|
previousQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer);
|
||||||
|
currentI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer + 1);
|
||||||
|
currentQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer + 1);
|
||||||
|
|
||||||
|
demodI = currentI.mul(previousI).sub(currentQ.mul(previousQ.neg()));
|
||||||
|
demodQ = currentQ.mul(previousI).add(currentI.mul(previousQ.neg()));
|
||||||
|
|
||||||
|
//Replace any zero-values in the I vector by adding Float.MIN_VALUE to side-step divide by zero errors
|
||||||
|
demodI = demodI.add(Float.MIN_VALUE, demodQ.eq(ZERO));
|
||||||
|
|
||||||
|
demod = demodQ.div(demodI).lanewise(VectorOperators.ATAN);
|
||||||
|
demod.intoArray(demodulated, bufferPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return demodulated;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.fm;
|
||||||
|
|
||||||
|
import io.github.dsheirer.vector.VectorUtilities;
|
||||||
|
import jdk.incubator.vector.FloatVector;
|
||||||
|
import jdk.incubator.vector.VectorOperators;
|
||||||
|
import jdk.incubator.vector.VectorSpecies;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FM demodulator that uses JDK 17+ SIMD vector intrinsics
|
||||||
|
*/
|
||||||
|
public class VectorFMDemodulator256 implements IDemodulator
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(VectorFMDemodulator256.class);
|
||||||
|
protected static final float ZERO = 0.0f;
|
||||||
|
private static final int BUFFER_OVERLAP = 1;
|
||||||
|
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||||
|
|
||||||
|
//Initialize buffers to be non-null and resize once the first buffer arrives
|
||||||
|
private float[] mIBuffer = new float[1];
|
||||||
|
private float[] mQBuffer = new float[1];
|
||||||
|
|
||||||
|
public VectorFMDemodulator256()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public float[] demodulate(float[] i, float[] q)
|
||||||
|
{
|
||||||
|
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||||
|
|
||||||
|
if(mIBuffer.length != (i.length + BUFFER_OVERLAP))
|
||||||
|
{
|
||||||
|
mIBuffer = new float[i.length + BUFFER_OVERLAP];
|
||||||
|
mQBuffer = new float[q.length + BUFFER_OVERLAP];
|
||||||
|
}
|
||||||
|
|
||||||
|
//Copy last sample to beginning of buffer arrays
|
||||||
|
mIBuffer[0] = mIBuffer[mIBuffer.length - 1];
|
||||||
|
mQBuffer[0] = mQBuffer[mQBuffer.length - 1];
|
||||||
|
|
||||||
|
//Copy new samples to end of buffer arrays
|
||||||
|
System.arraycopy(i, 0, mIBuffer, 1, i.length);
|
||||||
|
System.arraycopy(q, 0, mQBuffer, 1, q.length);
|
||||||
|
|
||||||
|
float[] demodulated = new float[i.length];
|
||||||
|
|
||||||
|
FloatVector currentI, currentQ, previousI, previousQ, demod, demodI, demodQ;
|
||||||
|
|
||||||
|
for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += VECTOR_SPECIES.length())
|
||||||
|
{
|
||||||
|
previousI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer);
|
||||||
|
previousQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer);
|
||||||
|
currentI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer + 1);
|
||||||
|
currentQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer + 1);
|
||||||
|
|
||||||
|
demodI = currentI.mul(previousI).sub(currentQ.mul(previousQ.neg()));
|
||||||
|
demodQ = currentQ.mul(previousI).add(currentI.mul(previousQ.neg()));
|
||||||
|
|
||||||
|
//Replace any zero-values in the I vector by adding Float.MIN_VALUE to side-step divide by zero errors
|
||||||
|
demodI = demodI.add(Float.MIN_VALUE, demodQ.eq(ZERO));
|
||||||
|
|
||||||
|
demod = demodQ.div(demodI).lanewise(VectorOperators.ATAN);
|
||||||
|
demod.intoArray(demodulated, bufferPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return demodulated;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.fm;
|
||||||
|
|
||||||
|
import io.github.dsheirer.vector.VectorUtilities;
|
||||||
|
import jdk.incubator.vector.FloatVector;
|
||||||
|
import jdk.incubator.vector.VectorOperators;
|
||||||
|
import jdk.incubator.vector.VectorSpecies;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FM demodulator that uses JDK 17+ SIMD vector intrinsics
|
||||||
|
*/
|
||||||
|
public class VectorFMDemodulator512 implements IDemodulator
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(VectorFMDemodulator512.class);
|
||||||
|
protected static final float ZERO = 0.0f;
|
||||||
|
private static final int BUFFER_OVERLAP = 1;
|
||||||
|
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||||
|
|
||||||
|
//Initialize buffers to be non-null and resize once the first buffer arrives
|
||||||
|
private float[] mIBuffer = new float[1];
|
||||||
|
private float[] mQBuffer = new float[1];
|
||||||
|
|
||||||
|
public VectorFMDemodulator512()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public float[] demodulate(float[] i, float[] q)
|
||||||
|
{
|
||||||
|
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||||
|
|
||||||
|
if(mIBuffer.length != (i.length + BUFFER_OVERLAP))
|
||||||
|
{
|
||||||
|
mIBuffer = new float[i.length + BUFFER_OVERLAP];
|
||||||
|
mQBuffer = new float[q.length + BUFFER_OVERLAP];
|
||||||
|
}
|
||||||
|
|
||||||
|
//Copy last sample to beginning of buffer arrays
|
||||||
|
mIBuffer[0] = mIBuffer[mIBuffer.length - 1];
|
||||||
|
mQBuffer[0] = mQBuffer[mQBuffer.length - 1];
|
||||||
|
|
||||||
|
//Copy new samples to end of buffer arrays
|
||||||
|
System.arraycopy(i, 0, mIBuffer, 1, i.length);
|
||||||
|
System.arraycopy(q, 0, mQBuffer, 1, q.length);
|
||||||
|
|
||||||
|
float[] demodulated = new float[i.length];
|
||||||
|
|
||||||
|
FloatVector currentI, currentQ, previousI, previousQ, demod, demodI, demodQ;
|
||||||
|
|
||||||
|
for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += VECTOR_SPECIES.length())
|
||||||
|
{
|
||||||
|
previousI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer);
|
||||||
|
previousQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer);
|
||||||
|
currentI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer + 1);
|
||||||
|
currentQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer + 1);
|
||||||
|
|
||||||
|
demodI = currentI.mul(previousI).sub(currentQ.mul(previousQ.neg()));
|
||||||
|
demodQ = currentQ.mul(previousI).add(currentI.mul(previousQ.neg()));
|
||||||
|
|
||||||
|
//Replace any zero-values in the I vector by adding Float.MIN_VALUE to side-step divide by zero errors
|
||||||
|
demodI = demodI.add(Float.MIN_VALUE, demodQ.eq(ZERO));
|
||||||
|
|
||||||
|
demod = demodQ.div(demodI).lanewise(VectorOperators.ATAN);
|
||||||
|
demod.intoArray(demodulated, bufferPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return demodulated;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.fm;
|
||||||
|
|
||||||
|
import io.github.dsheirer.vector.VectorUtilities;
|
||||||
|
import jdk.incubator.vector.FloatVector;
|
||||||
|
import jdk.incubator.vector.VectorOperators;
|
||||||
|
import jdk.incubator.vector.VectorSpecies;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FM demodulator that uses JDK 17+ SIMD vector intrinsics
|
||||||
|
*/
|
||||||
|
public class VectorFMDemodulator64 implements IDemodulator
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(VectorFMDemodulator64.class);
|
||||||
|
protected static final float ZERO = 0.0f;
|
||||||
|
private static final int BUFFER_OVERLAP = 1;
|
||||||
|
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||||
|
|
||||||
|
//Initialize buffers to be non-null and resize once the first buffer arrives
|
||||||
|
private float[] mIBuffer = new float[1];
|
||||||
|
private float[] mQBuffer = new float[1];
|
||||||
|
|
||||||
|
public VectorFMDemodulator64()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public float[] demodulate(float[] i, float[] q)
|
||||||
|
{
|
||||||
|
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||||
|
|
||||||
|
if(mIBuffer.length != (i.length + BUFFER_OVERLAP))
|
||||||
|
{
|
||||||
|
mIBuffer = new float[i.length + BUFFER_OVERLAP];
|
||||||
|
mQBuffer = new float[q.length + BUFFER_OVERLAP];
|
||||||
|
}
|
||||||
|
|
||||||
|
//Copy last sample to beginning of buffer arrays
|
||||||
|
mIBuffer[0] = mIBuffer[mIBuffer.length - 1];
|
||||||
|
mQBuffer[0] = mQBuffer[mQBuffer.length - 1];
|
||||||
|
|
||||||
|
//Copy new samples to end of buffer arrays
|
||||||
|
System.arraycopy(i, 0, mIBuffer, 1, i.length);
|
||||||
|
System.arraycopy(q, 0, mQBuffer, 1, q.length);
|
||||||
|
|
||||||
|
float[] demodulated = new float[i.length];
|
||||||
|
|
||||||
|
FloatVector currentI, currentQ, previousI, previousQ, demod, demodI, demodQ;
|
||||||
|
|
||||||
|
for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += VECTOR_SPECIES.length())
|
||||||
|
{
|
||||||
|
previousI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer);
|
||||||
|
previousQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer);
|
||||||
|
currentI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer + 1);
|
||||||
|
currentQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer + 1);
|
||||||
|
|
||||||
|
demodI = currentI.mul(previousI).sub(currentQ.mul(previousQ.neg()));
|
||||||
|
demodQ = currentQ.mul(previousI).add(currentI.mul(previousQ.neg()));
|
||||||
|
|
||||||
|
//Replace any zero-values in the I vector by adding Float.MIN_VALUE to side-step divide by zero errors
|
||||||
|
demodI = demodI.add(Float.MIN_VALUE, demodQ.eq(ZERO));
|
||||||
|
|
||||||
|
demod = demodQ.div(demodI).lanewise(VectorOperators.ATAN);
|
||||||
|
demod.intoArray(demodulated, bufferPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return demodulated;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,229 +0,0 @@
|
|||||||
/*
|
|
||||||
* *****************************************************************************
|
|
||||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* ****************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.github.dsheirer.dsp.fm;
|
|
||||||
|
|
||||||
import io.github.dsheirer.dsp.squelch.PowerSquelch;
|
|
||||||
import io.github.dsheirer.sample.Listener;
|
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
|
||||||
import io.github.dsheirer.vector.VectorUtilities;
|
|
||||||
import jdk.incubator.vector.FloatVector;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FM Demodulator for demodulating complex samples and producing demodulated floating point samples.
|
|
||||||
*
|
|
||||||
* Implements listener of source events to process runtime squelch threshold change request events
|
|
||||||
* which are forwarded to the power squelch control.
|
|
||||||
*/
|
|
||||||
public class VectorSquelchingFMDemodulator extends VectorFMDemodulator
|
|
||||||
implements ISquelchingFmDemodulator, Listener<SourceEvent>
|
|
||||||
{
|
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(VectorSquelchingFMDemodulator.class);
|
|
||||||
private static final float TWO = 2.0f;
|
|
||||||
private PowerSquelch mPowerSquelch;
|
|
||||||
private boolean mSquelchChanged = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an FM demodulator instance with a default gain of 1.0.
|
|
||||||
*/
|
|
||||||
public VectorSquelchingFMDemodulator(float alpha, float threshold, int ramp)
|
|
||||||
{
|
|
||||||
mPowerSquelch = new PowerSquelch(alpha, threshold, ramp);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers the listener to receive notifications of squelch change events from the power squelch.
|
|
||||||
*/
|
|
||||||
public void setSourceEventListener(Listener<SourceEvent> listener)
|
|
||||||
{
|
|
||||||
mPowerSquelch.setSourceEventListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Demodulates the complex (I/Q) sample arrays
|
|
||||||
* @param i inphase samples
|
|
||||||
* @param q quadrature samples
|
|
||||||
* @return demodulated real samples
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public float[] demodulate(float[] i, float[] q)
|
|
||||||
{
|
|
||||||
setSquelchChanged(false);
|
|
||||||
|
|
||||||
float[] demodulated = super.demodulate(i, q);
|
|
||||||
float[] magnitude = getMagnitude(i, q);
|
|
||||||
|
|
||||||
for(int x = 0; x < i.length; x++)
|
|
||||||
{
|
|
||||||
mPowerSquelch.process(magnitude[x]);
|
|
||||||
|
|
||||||
if(!(mPowerSquelch.isUnmuted() || mPowerSquelch.isDecay()))
|
|
||||||
{
|
|
||||||
demodulated[x] = ZERO;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(mPowerSquelch.isSquelchChanged())
|
|
||||||
{
|
|
||||||
setSquelchChanged(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return demodulated;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the magnitude of the complex I/Q sample arrays using SIMD vector intrinsics.
|
|
||||||
* @param i sample array
|
|
||||||
* @param q sample array
|
|
||||||
* @return magnitude array
|
|
||||||
*/
|
|
||||||
public static float[] getMagnitude(float[] i, float[] q)
|
|
||||||
{
|
|
||||||
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
|
||||||
|
|
||||||
float[] magnitudes = new float[i.length];
|
|
||||||
FloatVector iVector, qVector;
|
|
||||||
|
|
||||||
for(int samplePointer = 0; samplePointer < i.length; samplePointer += VECTOR_SPECIES.length())
|
|
||||||
{
|
|
||||||
iVector = FloatVector.fromArray(VECTOR_SPECIES, i, samplePointer);
|
|
||||||
qVector = FloatVector.fromArray(VECTOR_SPECIES, q, samplePointer);
|
|
||||||
iVector.pow(TWO).add(qVector.pow(TWO)).intoArray(magnitudes, samplePointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return magnitudes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the threshold for squelch control
|
|
||||||
* @param threshold (dB)
|
|
||||||
*/
|
|
||||||
public void setSquelchThreshold(double threshold)
|
|
||||||
{
|
|
||||||
mPowerSquelch.setSquelchThreshold(threshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the squelch state has changed during the processing of buffer(s)
|
|
||||||
*/
|
|
||||||
public boolean isSquelchChanged()
|
|
||||||
{
|
|
||||||
return mSquelchChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets or resets the squelch changed flag.
|
|
||||||
*/
|
|
||||||
private void setSquelchChanged(boolean changed)
|
|
||||||
{
|
|
||||||
mSquelchChanged = changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the squelch state is currently muted
|
|
||||||
*/
|
|
||||||
public boolean isMuted()
|
|
||||||
{
|
|
||||||
return mPowerSquelch.isMuted();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receive(SourceEvent sourceEvent)
|
|
||||||
{
|
|
||||||
//Only forward squelch threshold change request events
|
|
||||||
if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD)
|
|
||||||
{
|
|
||||||
mPowerSquelch.receive(sourceEvent);
|
|
||||||
}
|
|
||||||
else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD)
|
|
||||||
{
|
|
||||||
mPowerSquelch.receive(sourceEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
int iterations = 3_000_000;
|
|
||||||
int sampleSize = 2048;
|
|
||||||
|
|
||||||
Random random = new Random();
|
|
||||||
|
|
||||||
float[] iSamples = new float[sampleSize];
|
|
||||||
float[] qSamples = new float[sampleSize];
|
|
||||||
for(int x = 0; x < iSamples.length; x++)
|
|
||||||
{
|
|
||||||
iSamples[x] = random.nextFloat() * 2.0f - 1.0f;
|
|
||||||
qSamples[x] = random.nextFloat() * 2.0f - 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
float POWER_SQUELCH_ALPHA_DECAY = 0.0004f;
|
|
||||||
float POWER_SQUELCH_THRESHOLD_DB = -78.0f;
|
|
||||||
int POWER_SQUELCH_RAMP = 4;
|
|
||||||
|
|
||||||
ScalarFMDemodulator fm = new ScalarFMDemodulator();
|
|
||||||
VectorFMDemodulator vectorFM = new VectorFMDemodulator();
|
|
||||||
ScalarSquelchingFMDemodulator sqLegacy = new ScalarSquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP);
|
|
||||||
VectorSquelchingFMDemodulator vectorSq = new VectorSquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP);
|
|
||||||
|
|
||||||
boolean validation = false;
|
|
||||||
|
|
||||||
if(validation)
|
|
||||||
{
|
|
||||||
float[] noSqFmSamples = fm.demodulate(iSamples, qSamples);
|
|
||||||
float[] vectorNoSqFmSamples = vectorFM.demodulate(iSamples, qSamples);
|
|
||||||
float[] sqFmSamples = sqLegacy.demodulate(iSamples, qSamples);
|
|
||||||
float[] vectorSqFmSamples = vectorSq.demodulate(iSamples, qSamples);
|
|
||||||
System.out.println(" NO SQ FM:" + Arrays.toString(noSqFmSamples));
|
|
||||||
System.out.println("VECTOR NO SQ FM:" + Arrays.toString(vectorNoSqFmSamples));
|
|
||||||
System.out.println(" SQ FM:" + Arrays.toString(sqFmSamples));
|
|
||||||
System.out.println(" VECTOR SQ FM:" + Arrays.toString(vectorSqFmSamples));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
System.out.println("Test Starting ...");
|
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
|
|
||||||
double accumulator = 0.0;
|
|
||||||
|
|
||||||
for(int i = 0; i < iterations; i++)
|
|
||||||
{
|
|
||||||
// float[] samples = sqLegacy.demodulate(iSamples, qSamples);
|
|
||||||
float[] samples = vectorSq.demodulate(iSamples, qSamples);
|
|
||||||
accumulator += samples[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
double elapsed = System.currentTimeMillis() - start;
|
|
||||||
|
|
||||||
DecimalFormat df = new DecimalFormat("0.000");
|
|
||||||
System.out.println("Accumulator: " + accumulator);
|
|
||||||
System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
@@ -16,61 +16,64 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* ****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.github.dsheirer.dsp.fsk;
|
package io.github.dsheirer.dsp.fsk;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.MessageFramer;
|
|
||||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||||
import io.github.dsheirer.dsp.filter.dc.IIRSinglePoleDCRemovalFilter;
|
|
||||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||||
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||||
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
|
||||||
import io.github.dsheirer.dsp.symbol.ISyncDetectListener;
|
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import java.util.Arrays;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logic Trunked Radio (LTR) 300-baud FSK decoder is designed to operate against an 8 kHz FM demodulated audio stream.
|
* LTR decoder designed to work with 8 kHz input audio samples and decode 300-baud signalling.
|
||||||
*
|
*
|
||||||
* This decoder employs a zero-crossing timing error detector with adjustable symbol timing error gain. This decoder
|
* This decoder monitors the incoming sample stream looking for either a positive or negative sequence of samples where
|
||||||
* also employs a synchronization monitor that automatically adjusts symbol timing error gain according to the quantity
|
* the energy delta across the baud period exceeds a threshold value and causes the symbol state to flip. The symbol
|
||||||
* of successful message sync pattern detections. This is optimized for LTR signals that periodically transmit idle
|
* state remains across successive baud periods until the next state change sequence is detected.
|
||||||
* bursts and transmit continuous message streams during voice transmissions. The synchronization monitor adjusts to an
|
|
||||||
* aggressive gain value when sync is lost and automatically reduces timing error gain as successive sync pattern
|
|
||||||
* detections occur.
|
|
||||||
*
|
*
|
||||||
* FM demodulated sample buffers should not be filtered prior to this decoder. An internal DC-removal filter coupled
|
* Valid LTR messages start with a zero symbol and transition to a positive symbol for the sync bit, this decoder
|
||||||
* with a low-pass filter removes any tuning offset and/or audio components above 300 Hertz. The DC removal filter is
|
* monitors for excessively long sequences of a one symbol and forces the symbol state back to a zero.
|
||||||
* adjusted for a slower offset removal in order to prevent overly distorting the FSK signalling component. This mild
|
|
||||||
* adjustment may not fully compensate for mis-tuned signals and may result in missed decodes of idle bursts. However,
|
|
||||||
* the DC filter will track a mis-tuned signal to baseband quickly upon a continuous transmission.
|
|
||||||
*/
|
*/
|
||||||
public class LTRDecoder implements Listener<float[]>, ISyncDetectListener, ISyncStateListener
|
public class LTRDecoder implements Listener<float[]>
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(LTRDecoder.class);
|
private static final Logger mLog = LoggerFactory.getLogger(LTRDecoder.class);
|
||||||
|
//Slope calculation sumXX value is always the same for 27 evenly spaced samples
|
||||||
public static final float SAMPLES_PER_SYMBOL = 8000.0f / 300.0f;
|
private static final double SLOPE_CALCULATION_SUM_XX = 1637.999992057681;
|
||||||
|
private static final float BAUD_LENGTH = 8000.0f / 300.0f;
|
||||||
protected static final float COARSE_TIMING_GAIN = 1.0f / 3.0f;
|
private static final float SLOPE_THRESHOLD = 0.0048f;
|
||||||
protected static final float MEDIUM_TIMING_GAIN = 1.0f / 4.0f;
|
private static final int MAX_ONES_SEQUENCE = 18;
|
||||||
protected static final float FINE_TIMING_GAIN = 1.0f / 5.0f;
|
private static final int OVERLAP = 26;
|
||||||
|
private static final int POST_TRANSITION_SAMPLES_TO_SKIP = 26;
|
||||||
|
private static final int SLOPE_CALCULATION_LENGTH = 27;
|
||||||
|
private static final int SYMBOL_TRANSITION_IDEAL_MIN = 11;
|
||||||
|
private static final int SYMBOL_TRANSITION_IDEAL_MAX = 16;
|
||||||
|
|
||||||
private static float[] sLowPassFilterCoefficients;
|
private static float[] sLowPassFilterCoefficients;
|
||||||
|
private boolean mSymbol = false;
|
||||||
|
private float[] mResidual = new float[OVERLAP];
|
||||||
|
private float mBaudCounter = 0f;
|
||||||
|
private float mMaxSlope = 0;
|
||||||
|
private int mExcessiveOneSequenceCounter = 0;
|
||||||
|
private Listener<boolean[]> mSymbolListener;
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||||
.sampleRate(8000)
|
.sampleRate(8000)
|
||||||
.gridDensity(16)
|
.gridDensity(16)
|
||||||
.oddLength(true)
|
.oddLength(true)
|
||||||
.passBandCutoff(300)
|
.passBandCutoff(300)
|
||||||
.passBandAmplitude(1.0)
|
.passBandAmplitude(1.0)
|
||||||
.passBandRipple(0.01)
|
.passBandRipple(0.01)
|
||||||
.stopBandStart(500)
|
.stopBandStart(500)
|
||||||
.stopBandAmplitude(0.0)
|
.stopBandAmplitude(0.0)
|
||||||
.stopBandRipple(0.03) //Approximately 60 dB attenuation
|
.stopBandRipple(0.03) //Approximately 60 dB attenuation
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -87,143 +90,193 @@ public class LTRDecoder implements Listener<float[]>, ISyncDetectListener, ISync
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected float mSymbolTimingGain = COARSE_TIMING_GAIN;
|
|
||||||
protected SampleBuffer mSampleBuffer;
|
|
||||||
protected ZeroCrossingErrorDetector mTimingErrorDetector = new ZeroCrossingErrorDetector(SAMPLES_PER_SYMBOL);
|
|
||||||
protected SynchronizationMonitor mSynchronizationMonitor;
|
|
||||||
private IIRSinglePoleDCRemovalFilter mDCFilter = new IIRSinglePoleDCRemovalFilter(0.99999f);
|
|
||||||
private IRealFilter mLowPassFilter = FilterFactory.getRealFilter(sLowPassFilterCoefficients);
|
private IRealFilter mLowPassFilter = FilterFactory.getRealFilter(sLowPassFilterCoefficients);
|
||||||
private MessageFramer mMessageFramer;
|
|
||||||
|
|
||||||
private boolean mSampleDecision;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling decoder
|
* Constructs an instance
|
||||||
*
|
|
||||||
* @param messageLength in symbols
|
|
||||||
*/
|
*/
|
||||||
public LTRDecoder(int messageLength)
|
public LTRDecoder()
|
||||||
{
|
{
|
||||||
this(messageLength, new SampleBuffer(SAMPLES_PER_SYMBOL, COARSE_TIMING_GAIN),
|
|
||||||
new ZeroCrossingErrorDetector(SAMPLES_PER_SYMBOL));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a Logic Trunked Radio sub-audible 300 baud FSK signaling decoder.
|
* Registers the listener to receive decoded symbols.
|
||||||
*
|
* @param listener to register
|
||||||
* @param messageLength in symbols
|
|
||||||
* @param sampleBuffer to use for storing sample decisions
|
|
||||||
* @param detector for symbol timing errors
|
|
||||||
*/
|
*/
|
||||||
public LTRDecoder(int messageLength, SampleBuffer sampleBuffer, ZeroCrossingErrorDetector detector)
|
public void setListener(Listener<boolean[]> listener)
|
||||||
{
|
{
|
||||||
mSynchronizationMonitor = new SynchronizationMonitor(messageLength);
|
mSymbolListener = listener;
|
||||||
mSynchronizationMonitor.setListener(this);
|
|
||||||
mTimingErrorDetector = detector;
|
|
||||||
mSampleBuffer = sampleBuffer;
|
|
||||||
mSampleBuffer.setTimingGain(mSymbolTimingGain);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the ISyncDetectedListener interface to be notified of message sync detection events.
|
* Processes the demodulated 8 kHz audio samples to extract the 300-baud LTR signalling and delivers the decoded
|
||||||
*
|
* symbol array to the registered listener.
|
||||||
* This allows the internal timing error detector to adjust symbol timing error gain levels according to the
|
* @param samples to demodulate.
|
||||||
* message synchronization state to quickly adjust to initial signal streams or to reduce gain levels once
|
|
||||||
* synchronization has been achieved.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
public void receive(float[] samples)
|
||||||
public void syncDetected(int bitErrors)
|
|
||||||
{
|
{
|
||||||
mSynchronizationMonitor.syncDetected(bitErrors);
|
if(mSymbolListener != null)
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void syncLost(int bitsProcessed)
|
|
||||||
{
|
|
||||||
//no-op
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes the buffer samples by converting all samples to boolean values reflecting if the sample value is
|
|
||||||
* greater than zero (or not). Average symbol timing offset is calculated for the full buffer and the offset is
|
|
||||||
* adjusted and then each of the symbols are decoded using a simple majority decision.
|
|
||||||
*
|
|
||||||
* @param buffer containing 8.0 kHz unfiltered FM demodulated audio samples with sub-audible LTR signalling.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void receive(float[] buffer)
|
|
||||||
{
|
|
||||||
float[] dcFiltered = mDCFilter.filter(buffer);
|
|
||||||
float[] lowPassFiltered = mLowPassFilter.filter(dcFiltered);
|
|
||||||
|
|
||||||
for(float sample : lowPassFiltered)
|
|
||||||
{
|
{
|
||||||
mSampleDecision = sample > 0.0;
|
float[] filtered = mLowPassFilter.filter(samples);
|
||||||
|
|
||||||
mSampleBuffer.receive(mSampleDecision);
|
int timingAdjust = 0;
|
||||||
mTimingErrorDetector.receive(mSampleDecision);
|
int samplesToSkip = 0;
|
||||||
|
|
||||||
if(mSampleBuffer.hasSymbol())
|
float[] buffer = new float[filtered.length + OVERLAP];
|
||||||
|
boolean[] symbols = new boolean[(int)((filtered.length + mBaudCounter) / BAUD_LENGTH)];
|
||||||
|
int symbolPointer = 0;
|
||||||
|
float slope = 0;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if(mMessageFramer != null)
|
System.arraycopy(mResidual, 0, buffer, 0, mResidual.length);
|
||||||
|
System.arraycopy(filtered, 0, buffer, mResidual.length, filtered.length);
|
||||||
|
System.arraycopy(filtered, filtered.length - OVERLAP, mResidual, 0, OVERLAP);
|
||||||
|
|
||||||
|
for(int bufferPointer = 0; bufferPointer < samples.length; bufferPointer++)
|
||||||
{
|
{
|
||||||
mMessageFramer.process(mSampleBuffer.getSymbol());
|
//Don't calculate slope if we're within the baud period following last symbol transition
|
||||||
|
if(samplesToSkip > 0)
|
||||||
|
{
|
||||||
|
samplesToSkip--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
slope = calculateSlope(buffer, bufferPointer);
|
||||||
|
|
||||||
|
if(mSymbol)
|
||||||
|
{
|
||||||
|
//Loop for crest of slope at lowest point below the negative slope threshold for transition
|
||||||
|
if(slope > mMaxSlope && mMaxSlope < -SLOPE_THRESHOLD)
|
||||||
|
{
|
||||||
|
mSymbol = false;
|
||||||
|
|
||||||
|
//Coarse symbol timing adjust - ideal toggle is middle of baud, between 11 & 16 samples
|
||||||
|
if(mBaudCounter < SYMBOL_TRANSITION_IDEAL_MIN)
|
||||||
|
{
|
||||||
|
timingAdjust = 1;
|
||||||
|
}
|
||||||
|
else if(mBaudCounter > SYMBOL_TRANSITION_IDEAL_MAX)
|
||||||
|
{
|
||||||
|
timingAdjust = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timingAdjust = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mBaudCounter += timingAdjust;
|
||||||
|
samplesToSkip = POST_TRANSITION_SAMPLES_TO_SKIP + timingAdjust;
|
||||||
|
}
|
||||||
|
else if(slope < mMaxSlope)
|
||||||
|
{
|
||||||
|
mMaxSlope = slope;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Loop for crest of slope at the highest point above the positive threshold
|
||||||
|
if(slope < mMaxSlope && mMaxSlope > SLOPE_THRESHOLD)
|
||||||
|
{
|
||||||
|
mSymbol = true;
|
||||||
|
|
||||||
|
//Coarse symbol timing adjust - ideal toggle is middle of baud, between 11 & 16 samples
|
||||||
|
if(mBaudCounter < SYMBOL_TRANSITION_IDEAL_MIN)
|
||||||
|
{
|
||||||
|
timingAdjust = 1;
|
||||||
|
}
|
||||||
|
else if(mBaudCounter > SYMBOL_TRANSITION_IDEAL_MAX)
|
||||||
|
{
|
||||||
|
timingAdjust = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timingAdjust = 0;
|
||||||
|
}
|
||||||
|
mBaudCounter += timingAdjust;
|
||||||
|
samplesToSkip = 26 + timingAdjust;
|
||||||
|
}
|
||||||
|
else if(slope > mMaxSlope)
|
||||||
|
{
|
||||||
|
mMaxSlope = slope;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mBaudCounter++;
|
||||||
|
|
||||||
|
if(mBaudCounter > BAUD_LENGTH)
|
||||||
|
{
|
||||||
|
//Expand symbols array length if needed to account for symbol timing adjustments
|
||||||
|
if(symbolPointer >= symbols.length)
|
||||||
|
{
|
||||||
|
symbols = Arrays.copyOf(symbols, symbols.length + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
symbols[symbolPointer++] = mSymbol;
|
||||||
|
|
||||||
|
mBaudCounter -= BAUD_LENGTH;
|
||||||
|
|
||||||
|
//Check for a continuous string of ones and flip the symbol state to zero.
|
||||||
|
if(mSymbol)
|
||||||
|
{
|
||||||
|
mExcessiveOneSequenceCounter++;
|
||||||
|
|
||||||
|
if(mExcessiveOneSequenceCounter > MAX_ONES_SEQUENCE) //Verify that this is the right threshold for a string of ones
|
||||||
|
{
|
||||||
|
mSymbol = false;
|
||||||
|
mMaxSlope = -1f;
|
||||||
|
mExcessiveOneSequenceCounter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mExcessiveOneSequenceCounter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.warn("Unexpected error while processing LTR samples", e);
|
||||||
|
}
|
||||||
|
|
||||||
mSampleBuffer.resetAndAdjust(-mTimingErrorDetector.getError());
|
//Truncate the symbols array if we didn't fill it completely, due to symbol timing adjustments.
|
||||||
|
if(symbolPointer != symbols.length)
|
||||||
mSynchronizationMonitor.increment();
|
{
|
||||||
|
mSymbolListener.receive(Arrays.copyOf(symbols, symbolPointer));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mSymbolListener.receive(symbols);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a listener to receive decoded LTR symbols.
|
* Calculates the slope of the 27 samples in the values array starting at the specified offset.
|
||||||
*
|
* @param samples to load from
|
||||||
* @param messageFramer to receive symbols.
|
* @param offset to the sample start for the slope calculation
|
||||||
*/
|
*/
|
||||||
public void setMessageFramer(MessageFramer messageFramer)
|
public float calculateSlope(float[] samples, int offset)
|
||||||
{
|
{
|
||||||
mMessageFramer = messageFramer;
|
/** mean of accumulated x & y values, used in updating formulas */
|
||||||
}
|
double xbar = 0;
|
||||||
|
double ybar = samples[offset];
|
||||||
|
|
||||||
/**
|
double sumXY = 0;
|
||||||
* Removes the symbol listener from receiving decoded LTR symbols.
|
double fact1, dx, dy;
|
||||||
*/
|
|
||||||
public void removeListener()
|
|
||||||
{
|
|
||||||
mMessageFramer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for(int x = 1; x < SLOPE_CALCULATION_LENGTH; x++)
|
||||||
/**
|
|
||||||
* Implements the ISyncStateListener interface to receive synchronization state events and adjust the symbol timing
|
|
||||||
* error gain levels on the internal timing error detector.
|
|
||||||
*
|
|
||||||
* Gain levels are defined relative to a unit gain of 1.0 over a number of symbol periods. LTR uses an initial
|
|
||||||
* ramp-up symbol reversal pattern of 0101 which provides three zero crossing detection opportunities for the timing
|
|
||||||
* error detector, therefore we react to a COARSE gain state to average timing error over 3 symbols.
|
|
||||||
*
|
|
||||||
* @param syncState from an external synchronization state monitor
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setSyncState(SyncState syncState)
|
|
||||||
{
|
|
||||||
switch(syncState)
|
|
||||||
{
|
{
|
||||||
case FINE:
|
fact1 = 1.0f + x;
|
||||||
mSymbolTimingGain = FINE_TIMING_GAIN;
|
dx = x - xbar;
|
||||||
break;
|
dy = samples[offset + x] - ybar;
|
||||||
case MEDIUM:
|
sumXY += dx * dy * (x / (1.0f + x));
|
||||||
mSymbolTimingGain = MEDIUM_TIMING_GAIN;
|
xbar += dx / fact1;
|
||||||
break;
|
ybar += dy / fact1;
|
||||||
case COARSE:
|
|
||||||
mSymbolTimingGain = COARSE_TIMING_GAIN;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unrecognized sync state level: " + syncState.name());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mSampleBuffer.setTimingGain(mSymbolTimingGain);
|
return (float)(sumXY / SLOPE_CALCULATION_SUM_XX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,30 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* sdr-trunk
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2018 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.dsp.fsk;
|
package io.github.dsheirer.dsp.fsk;
|
||||||
|
|
||||||
public class LTRDecoderInstrumented extends LTRDecoder
|
public class LTRDecoderInstrumented extends LTRDecoder
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Instrumented extension to LTRDecoder
|
* Instrumented extension to LTRDecoder
|
||||||
*
|
|
||||||
* @param messageLength in symbols
|
|
||||||
*/
|
*/
|
||||||
public LTRDecoderInstrumented(int messageLength)
|
public LTRDecoderInstrumented()
|
||||||
{
|
{
|
||||||
super(messageLength, new SampleBufferInstrumented(SAMPLES_PER_SYMBOL, COARSE_TIMING_GAIN),
|
super();
|
||||||
new ZeroCrossingErrorDetectorInstrumented(SAMPLES_PER_SYMBOL));
|
|
||||||
}
|
|
||||||
|
|
||||||
public SampleBufferInstrumented getSampleBuffer()
|
|
||||||
{
|
|
||||||
return (SampleBufferInstrumented)mSampleBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ZeroCrossingErrorDetectorInstrumented getErrorDetector()
|
|
||||||
{
|
|
||||||
return (ZeroCrossingErrorDetectorInstrumented)mTimingErrorDetector;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.gain;
|
||||||
|
|
||||||
|
import org.apache.commons.math3.util.FastMath;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio gain that normalizes audio amplitude against an objective amplitude value where the audio amplitude values
|
||||||
|
* normally fall in the range of -1.0 to 1.0.
|
||||||
|
*
|
||||||
|
* Removes DC bias on a per audio sample buffer basis.
|
||||||
|
*
|
||||||
|
* This control is designed to work on a per-call basis by monitoring the maximum observed amplitude and adjusting the
|
||||||
|
* gain to normalize the amplitude toward the objective amplitude value. This control is designed to be reset at the
|
||||||
|
* beginning/end of each call segment so that the max observed amplitude is relevant to the current call segment.
|
||||||
|
*
|
||||||
|
* Min and max gains should be sized to constrain the applied value during periods of extended silence at the
|
||||||
|
* beginning of an audio segment or where a single amplitude spike might erroneously affect gain across the segment.
|
||||||
|
*/
|
||||||
|
public class AudioGainAndDcFilter
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(AudioGainAndDcFilter.class);
|
||||||
|
private static final float MAX_AMPLITUDE = 0.95f;
|
||||||
|
private static final float GAIN_LOOP_BANDWIDTH = 0.0015f;
|
||||||
|
private float mMinGain;
|
||||||
|
private float mMaxGain;
|
||||||
|
private float mCurrentGain;
|
||||||
|
private float mObjectiveGain;
|
||||||
|
private float mObjectiveAmplitude;
|
||||||
|
private float mMaxObservedAmplitude;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param minGain to apply to the incoming sample stream.
|
||||||
|
* @param maxGain to apply
|
||||||
|
* @param objectiveAmplitude to achieve by adjusting gain between min and max.
|
||||||
|
*/
|
||||||
|
public AudioGainAndDcFilter(float minGain, float maxGain, float objectiveAmplitude)
|
||||||
|
{
|
||||||
|
mMinGain = minGain;
|
||||||
|
mMaxGain = maxGain;
|
||||||
|
mObjectiveAmplitude = objectiveAmplitude;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets this gain control to prepare for the next audio call/segment.
|
||||||
|
*/
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
mMaxObservedAmplitude = 0.0f;
|
||||||
|
mObjectiveGain = 1.0f;
|
||||||
|
mCurrentGain = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a buffer of audio samples and apply gain.
|
||||||
|
* @param samples to adjust.
|
||||||
|
* @return amplified audio samples
|
||||||
|
*/
|
||||||
|
public float[] process(float[] samples)
|
||||||
|
{
|
||||||
|
float currentAmplitude;
|
||||||
|
float dcAccumulator = 0.0f;
|
||||||
|
|
||||||
|
//Decay the max observed value by 10% each buffer so that an initial spike doesn't carry across all buffers
|
||||||
|
mMaxObservedAmplitude *= 0.9f;
|
||||||
|
|
||||||
|
for(float sample: samples)
|
||||||
|
{
|
||||||
|
dcAccumulator += sample;
|
||||||
|
currentAmplitude = FastMath.min(Math.abs(sample), 2.0f);
|
||||||
|
|
||||||
|
if(currentAmplitude > mMaxObservedAmplitude)
|
||||||
|
{
|
||||||
|
mMaxObservedAmplitude = currentAmplitude;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mObjectiveGain = mObjectiveAmplitude / mMaxObservedAmplitude;
|
||||||
|
|
||||||
|
if(mObjectiveGain > mMaxGain)
|
||||||
|
{
|
||||||
|
mObjectiveGain = mMaxGain;
|
||||||
|
}
|
||||||
|
else if(mObjectiveGain < mMinGain)
|
||||||
|
{
|
||||||
|
mObjectiveGain = mMinGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
float dcOffset = dcAccumulator / samples.length;
|
||||||
|
float gain = mCurrentGain;
|
||||||
|
float objective = mObjectiveGain;
|
||||||
|
float[] processed = new float[samples.length];
|
||||||
|
boolean objectiveAchieved = (gain == objective);
|
||||||
|
float amplified;
|
||||||
|
|
||||||
|
for(int x = 0; x < samples.length; x++)
|
||||||
|
{
|
||||||
|
if(!objectiveAchieved)
|
||||||
|
{
|
||||||
|
gain += ((objective - gain) * GAIN_LOOP_BANDWIDTH);
|
||||||
|
if(Math.abs(objective - gain) < 0.00005f)
|
||||||
|
{
|
||||||
|
gain = objective;
|
||||||
|
objectiveAchieved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
amplified = (samples[x] - dcOffset) * gain;
|
||||||
|
amplified = FastMath.min(amplified, MAX_AMPLITUDE);
|
||||||
|
amplified = FastMath.max(amplified, -MAX_AMPLITUDE);
|
||||||
|
processed[x] = amplified;
|
||||||
|
}
|
||||||
|
|
||||||
|
mCurrentGain = gain;
|
||||||
|
mObjectiveGain = objective;
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,88 +19,80 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.dsp.gain;
|
package io.github.dsheirer.dsp.gain;
|
||||||
|
|
||||||
import io.github.dsheirer.buffer.DoubleCircularBuffer;
|
import io.github.dsheirer.buffer.FloatCircularBuffer;
|
||||||
import io.github.dsheirer.buffer.RealCircularBuffer;
|
import io.github.dsheirer.source.wave.RealWaveSource;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
import org.apache.commons.math3.util.FastMath;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio automatic gain control.
|
||||||
|
*/
|
||||||
public class AutomaticGainControl
|
public class AutomaticGainControl
|
||||||
{
|
{
|
||||||
private static final double SAMPLE_RATE = 48000;
|
private static final Logger mLog = LoggerFactory.getLogger(AutomaticGainControl.class);
|
||||||
|
private static final float SAMPLE_RATE = 8000;
|
||||||
/* Signal delay line - time delay in seconds */
|
/* Signal delay line - time delay in seconds */
|
||||||
// private static final double DELAY_TIME_CONSTANT = 0.015;
|
private static final float DELAY_TIME_CONSTANT = 0.015f;
|
||||||
private static final double DELAY_TIME_CONSTANT = 0.01;
|
|
||||||
|
|
||||||
/* Peak detector window - time delay in seconds */
|
/* Peak detector window - time delay in seconds */
|
||||||
private static final double WINDOW_TIME_CONSTANT = 0.018;
|
private static final float WINDOW_TIME_CONSTANT = 0.018f;
|
||||||
|
|
||||||
/* Attack time constants in seconds */
|
/* Attack time constants in seconds */
|
||||||
private static final double ATTACK_RISE_TIME_CONSTANT = 0.002;
|
private static final float ATTACK_RISE_TIME_CONSTANT = 0.002f;
|
||||||
private static final double ATTACK_FALL_TIME_CONSTANT = 0.005;
|
private static final float ATTACK_FALL_TIME_CONSTANT = 0.005f;
|
||||||
|
private static final float ATTACK_RISE_ALPHA = 1.0f - (float)FastMath.exp(-1.0 / SAMPLE_RATE * ATTACK_RISE_TIME_CONSTANT);
|
||||||
private static final double ATTACK_RISE_ALPHA = 1.0 -
|
private static final float ATTACK_FALL_ALPHA = 1.0f - (float)FastMath.exp(-1.0 / SAMPLE_RATE * ATTACK_FALL_TIME_CONSTANT);
|
||||||
FastMath.exp(-1.0 / SAMPLE_RATE * ATTACK_RISE_TIME_CONSTANT);
|
|
||||||
|
|
||||||
private static final double ATTACK_FALL_ALPHA = 1.0 -
|
|
||||||
FastMath.exp(-1.0 / SAMPLE_RATE * ATTACK_FALL_TIME_CONSTANT);
|
|
||||||
|
|
||||||
/* AGC decay value in milliseconds (20 to 5000) */
|
/* AGC decay value in milliseconds (20 to 5000) */
|
||||||
private static final double DECAY = 200;
|
private static final float DECAY = 200.0f;
|
||||||
|
/* Ratio between rise and fall times of decay time constants - adjust for best action with SSB */
|
||||||
/* Ratio between rise and fall times of decay time constants - adjust for
|
private static final float DECAY_RISEFALL_RATIO = 0.3f;
|
||||||
* best action with SSB */
|
private static final float DECAY_RISE_ALPHA = 1.0f - (float)FastMath.exp(-1.0 / (SAMPLE_RATE * DECAY * .001 * DECAY_RISEFALL_RATIO));
|
||||||
private static final double DECAY_RISEFALL_RATIO = 0.3;
|
private static final float DECAY_FALL_ALPHA = 1.f - (float)FastMath.exp(-1.0 / (SAMPLE_RATE * DECAY * .001));
|
||||||
|
|
||||||
private static final double DECAY_RISE_ALPHA = 1.0 -
|
|
||||||
FastMath.exp(-1.0 / (SAMPLE_RATE * DECAY * .001 * DECAY_RISEFALL_RATIO));
|
|
||||||
|
|
||||||
private static final double DECAY_FALL_ALPHA = 1.0 -
|
|
||||||
FastMath.exp(-1.0 / (SAMPLE_RATE * DECAY * .001));
|
|
||||||
|
|
||||||
/* Hang timer release decay time constant in seconds */
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private static final double RELEASE_TIME_CONSTANT = 0.05;
|
|
||||||
|
|
||||||
/* Specifies the AGC Knee in dB if AGC is active (nominal range -160 to 0 dB) */
|
|
||||||
private static final double THRESHOLD = -100;
|
|
||||||
|
|
||||||
/* Limit output to about 3db of maximum */
|
|
||||||
private static final double AGC_OUT_SCALE = 0.7;
|
|
||||||
|
|
||||||
/* Keep max input and output the same */
|
/* Keep max input and output the same */
|
||||||
private static final double MAX_AMPLITUDE = 32767.0; //1.0;
|
private static final float MAX_AMPLITUDE = 1.0f; //1.0;
|
||||||
private static final double MAX_MANUAL_AMPLITUDE = 32767.0; //1.0;
|
private static final float MAX_MANUAL_AMPLITUDE = 1.0f; //1.0;
|
||||||
|
|
||||||
/* Specifies AGC manual gain in dB if AGC is not active ( 0 to 100 dB) */
|
/* Specifies AGC manual gain in dB if AGC is not active ( 0 to 100 dB) */
|
||||||
private static final double MANUAL_GAIN = 0.0;
|
private static final float MANUAL_GAIN = 0.0f;
|
||||||
|
private static final float MANUAL_AGC_GAIN = MAX_MANUAL_AMPLITUDE * (float)FastMath.pow(10.0, MANUAL_GAIN / 20.0);
|
||||||
private static final double MANUAL_AGC_GAIN = MAX_MANUAL_AMPLITUDE *
|
/* Limit output to about 3db of maximum */
|
||||||
FastMath.pow(10.0, MANUAL_GAIN / 20.0);
|
private static final float AGC_OUT_SCALE = 0.95f; //0.7f;
|
||||||
|
|
||||||
/* Specifies dB reduction in output at knee from max output level (0 - 10dB) */
|
/* Specifies dB reduction in output at knee from max output level (0 - 10dB) */
|
||||||
private static final double SLOPE_FACTOR = 2.0;
|
private static final float SLOPE_FACTOR = 2.0f;
|
||||||
|
/* Specifies the threshold when the AGC kicks in (nominal range -160 to 0 dB) */
|
||||||
private static final double KNEE = THRESHOLD / 20.0;
|
private static final float THRESHOLD = -20.0f; //-20.0f
|
||||||
|
private static final float KNEE = THRESHOLD / 20.0f; //20.0f
|
||||||
private static final double GAIN_SLOPE = SLOPE_FACTOR / 100.0;
|
private static final float GAIN_SLOPE = SLOPE_FACTOR / 100.0f;
|
||||||
|
private static final float FIXED_GAIN = AGC_OUT_SCALE * (float)FastMath.pow(10.0, KNEE * (GAIN_SLOPE - 1.0));
|
||||||
private static final double FIXED_GAIN = AGC_OUT_SCALE *
|
|
||||||
FastMath.pow(10.0, KNEE * (GAIN_SLOPE - 1.0));
|
|
||||||
|
|
||||||
/* Constant for calc log() so that a value of 0 magnitude = -8 */
|
/* Constant for calc log() so that a value of 0 magnitude = -8 */
|
||||||
private static final double MIN_CONSTANT = 3.2767E-4;
|
private static final float MIN_CONSTANT = 3.2767E-4f;
|
||||||
|
|
||||||
private boolean mAGCEnabled = true;
|
private boolean mAGCEnabled = true;
|
||||||
|
private float mPeakMagnitude = -5.0f;
|
||||||
|
private float mAttackAverage = 0.0f;
|
||||||
|
private float mDecayAverage = 0.0f;
|
||||||
|
private FloatCircularBuffer mDelayBuffer = new FloatCircularBuffer((int)(SAMPLE_RATE * DELAY_TIME_CONSTANT));
|
||||||
|
private FloatCircularBuffer mMagnitudeBuffer = new FloatCircularBuffer((int)(SAMPLE_RATE * WINDOW_TIME_CONSTANT), mPeakMagnitude);
|
||||||
|
private float mMaxGain = 0f;
|
||||||
|
private float mMinGain = 100000f;
|
||||||
|
|
||||||
private double mPeakMagnitude = 0.0;
|
/**
|
||||||
private double mAttackAverage = 0.0;
|
* Construct an instance
|
||||||
private double mDecayAverage = 0.0;
|
*/
|
||||||
|
|
||||||
private RealCircularBuffer mDelayBuffer = new RealCircularBuffer((int)(SAMPLE_RATE * DELAY_TIME_CONSTANT));
|
|
||||||
private DoubleCircularBuffer mMagnitudeBuffer = new DoubleCircularBuffer((int)(SAMPLE_RATE * WINDOW_TIME_CONSTANT));
|
|
||||||
|
|
||||||
public AutomaticGainControl()
|
public AutomaticGainControl()
|
||||||
{
|
{
|
||||||
|
System.out.println("Fixed gain: " + FIXED_GAIN + " Knee: " + KNEE + " Gain Slope:" + GAIN_SLOPE);
|
||||||
|
System.out.println("Manual gain: " + MANUAL_AGC_GAIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
mLog.info("Previous - Min Gain: " + mMinGain + " Max Gain: " + mMaxGain);
|
||||||
|
mLog.info("Resetting - Peak:" + mPeakMagnitude + " Attack:" + mAttackAverage + " Decay:" + mDecayAverage + " Mag Max:" + mMagnitudeBuffer.max(mPeakMagnitude));
|
||||||
|
mPeakMagnitude = -8.0f;
|
||||||
|
mAttackAverage = -8.0f;
|
||||||
|
mDecayAverage = -8.0f;
|
||||||
|
mDelayBuffer.reset(0.0f);
|
||||||
|
mMagnitudeBuffer.reset(mPeakMagnitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,15 +122,14 @@ public class AutomaticGainControl
|
|||||||
*/
|
*/
|
||||||
public float process(float currentSample)
|
public float process(float currentSample)
|
||||||
{
|
{
|
||||||
float delayedSample = mDelayBuffer.putAndGet(currentSample);
|
float delayedSample = mDelayBuffer.get(currentSample);
|
||||||
|
|
||||||
double gain = MANUAL_AGC_GAIN;
|
float gain = MANUAL_AGC_GAIN;
|
||||||
|
|
||||||
if(mAGCEnabled)
|
if(mAGCEnabled)
|
||||||
{
|
{
|
||||||
double currentMagnitude = FastMath.log10(FastMath.abs(currentSample) + MIN_CONSTANT) - FastMath.log10(MAX_AMPLITUDE);
|
float currentMagnitude = (float)(FastMath.log10(FastMath.abs(currentSample) + MIN_CONSTANT) - FastMath.log10(MAX_AMPLITUDE));
|
||||||
|
float delayedMagnitude = mMagnitudeBuffer.get(currentMagnitude);
|
||||||
double delayedMagnitude = mMagnitudeBuffer.get(currentMagnitude);
|
|
||||||
|
|
||||||
if(currentMagnitude > mPeakMagnitude)
|
if(currentMagnitude > mPeakMagnitude)
|
||||||
{
|
{
|
||||||
@@ -148,29 +139,30 @@ public class AutomaticGainControl
|
|||||||
else if(delayedMagnitude == mPeakMagnitude)
|
else if(delayedMagnitude == mPeakMagnitude)
|
||||||
{
|
{
|
||||||
/* If delayed magnitude is the current peak, then find a new peak */
|
/* If delayed magnitude is the current peak, then find a new peak */
|
||||||
mPeakMagnitude = mMagnitudeBuffer.max();
|
mPeakMagnitude = mMagnitudeBuffer.max(-3.4845634f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Exponential decay mode */
|
/* Exponential decay mode */
|
||||||
if(mPeakMagnitude > mAttackAverage)
|
if(mPeakMagnitude > mAttackAverage)
|
||||||
{
|
{
|
||||||
mAttackAverage = ((1.0 - ATTACK_RISE_ALPHA) * mAttackAverage) + (ATTACK_RISE_ALPHA * mPeakMagnitude);
|
mAttackAverage = ((1.0f - ATTACK_RISE_ALPHA) * mAttackAverage) + (ATTACK_RISE_ALPHA * mPeakMagnitude);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mAttackAverage = ((1.0 - ATTACK_FALL_ALPHA) * mAttackAverage) + (ATTACK_FALL_ALPHA * mPeakMagnitude);
|
mAttackAverage = ((1.0f - ATTACK_FALL_ALPHA) * mAttackAverage) + (ATTACK_FALL_ALPHA * mPeakMagnitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mPeakMagnitude > mDecayAverage)
|
if(mPeakMagnitude > mDecayAverage)
|
||||||
{
|
{
|
||||||
mDecayAverage = ((1.0 - DECAY_RISE_ALPHA) * mDecayAverage) + (DECAY_RISE_ALPHA * mPeakMagnitude);
|
mDecayAverage = ((1.0f - DECAY_RISE_ALPHA) * mDecayAverage) + (DECAY_RISE_ALPHA * mPeakMagnitude);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mDecayAverage = ((1.0 - DECAY_FALL_ALPHA) * mDecayAverage) + (DECAY_RISE_ALPHA * mPeakMagnitude);
|
mDecayAverage = ((1.0f - DECAY_FALL_ALPHA) * mDecayAverage) + (DECAY_RISE_ALPHA * mPeakMagnitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
double magnitude = FastMath.max(mAttackAverage, mDecayAverage);
|
|
||||||
|
float magnitude = FastMath.max(mAttackAverage, mDecayAverage);
|
||||||
|
|
||||||
if(magnitude < KNEE)
|
if(magnitude < KNEE)
|
||||||
{
|
{
|
||||||
@@ -178,11 +170,24 @@ public class AutomaticGainControl
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
gain = AGC_OUT_SCALE * FastMath.pow(10.0, magnitude * (GAIN_SLOPE - 1.0));
|
gain = AGC_OUT_SCALE * (float)FastMath.pow(10.0, magnitude * (GAIN_SLOPE - 1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
System.out.println("Current Mag: " + currentMagnitude + " Delay Mag: " + delayedMagnitude +
|
||||||
|
" Peak:" + mPeakMagnitude + " Gain:" + gain +
|
||||||
|
" Attack Avg:" + mAttackAverage + " Decay Avg:" + mDecayAverage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (float)(delayedSample * gain);
|
if(gain < mMinGain)
|
||||||
|
{
|
||||||
|
mMinGain = gain;
|
||||||
|
}
|
||||||
|
if(gain > mMaxGain)
|
||||||
|
{
|
||||||
|
mMaxGain = gain;
|
||||||
|
}
|
||||||
|
|
||||||
|
return delayedSample * gain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -202,4 +207,5 @@ public class AutomaticGainControl
|
|||||||
{
|
{
|
||||||
return mAGCEnabled;
|
return mAGCEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.magnitude;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for implementations that calculate magnitude
|
||||||
|
*/
|
||||||
|
public interface IMagnitudeCalculator
|
||||||
|
{
|
||||||
|
float[] calculate(float[] i, float[] q);
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.magnitude;
|
||||||
|
|
||||||
|
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
||||||
|
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||||
|
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for creating magnitude implementations
|
||||||
|
*/
|
||||||
|
public class MagnitudeFactory
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(MagnitudeFactory.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a magnitude implementation using calibration data to select the optimal
|
||||||
|
* implementation from scalar and vector options.
|
||||||
|
* @return demodulator instance
|
||||||
|
*/
|
||||||
|
public static IMagnitudeCalculator getMagnitudeCalculator()
|
||||||
|
{
|
||||||
|
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.MAGNITUDE);
|
||||||
|
|
||||||
|
switch(implementation)
|
||||||
|
{
|
||||||
|
case VECTOR_SIMD_64:
|
||||||
|
return new VectorMagnitudeCalculator64();
|
||||||
|
case VECTOR_SIMD_128:
|
||||||
|
return new VectorMagnitudeCalculator128();
|
||||||
|
case VECTOR_SIMD_256:
|
||||||
|
return new VectorMagnitudeCalculator256();
|
||||||
|
case VECTOR_SIMD_512:
|
||||||
|
return new VectorMagnitudeCalculator512();
|
||||||
|
case SCALAR:
|
||||||
|
default:
|
||||||
|
return new ScalarMagnitudeCalculator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.magnitude;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scalar implementation of magnitude
|
||||||
|
*/
|
||||||
|
public class ScalarMagnitudeCalculator implements IMagnitudeCalculator
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public float[] calculate(float[] i, float[] q)
|
||||||
|
{
|
||||||
|
float[] magnitude = new float[i.length];
|
||||||
|
|
||||||
|
for(int x = 0; x < i.length; x++)
|
||||||
|
{
|
||||||
|
magnitude[x] = ((i[x] * i[x]) + (q[x] * q[x]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return magnitude;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.magnitude;
|
||||||
|
|
||||||
|
import io.github.dsheirer.vector.VectorUtilities;
|
||||||
|
import jdk.incubator.vector.FloatVector;
|
||||||
|
import jdk.incubator.vector.VectorSpecies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vector implementation of magnitude calculation.
|
||||||
|
*/
|
||||||
|
public class VectorMagnitudeCalculator128 implements IMagnitudeCalculator
|
||||||
|
{
|
||||||
|
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] calculate(float[] i, float[] q)
|
||||||
|
{
|
||||||
|
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||||
|
float[] magnitude = new float[i.length];
|
||||||
|
|
||||||
|
FloatVector iVector, qVector, result;
|
||||||
|
for(int x = 0; x < i.length; x += VECTOR_SPECIES.length())
|
||||||
|
{
|
||||||
|
iVector = FloatVector.fromArray(VECTOR_SPECIES, i, x);
|
||||||
|
qVector = FloatVector.fromArray(VECTOR_SPECIES, q, x);
|
||||||
|
result = iVector.mul(iVector).add(qVector.mul(qVector));
|
||||||
|
result.intoArray(magnitude, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return magnitude;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.magnitude;
|
||||||
|
|
||||||
|
import io.github.dsheirer.vector.VectorUtilities;
|
||||||
|
import jdk.incubator.vector.FloatVector;
|
||||||
|
import jdk.incubator.vector.VectorSpecies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vector implementation of magnitude calculation.
|
||||||
|
*/
|
||||||
|
public class VectorMagnitudeCalculator256 implements IMagnitudeCalculator
|
||||||
|
{
|
||||||
|
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] calculate(float[] i, float[] q)
|
||||||
|
{
|
||||||
|
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||||
|
float[] magnitude = new float[i.length];
|
||||||
|
|
||||||
|
FloatVector iVector, qVector, result;
|
||||||
|
for(int x = 0; x < i.length; x += VECTOR_SPECIES.length())
|
||||||
|
{
|
||||||
|
iVector = FloatVector.fromArray(VECTOR_SPECIES, i, x);
|
||||||
|
qVector = FloatVector.fromArray(VECTOR_SPECIES, q, x);
|
||||||
|
result = iVector.mul(iVector).add(qVector.mul(qVector));
|
||||||
|
result.intoArray(magnitude, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return magnitude;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.magnitude;
|
||||||
|
|
||||||
|
import io.github.dsheirer.vector.VectorUtilities;
|
||||||
|
import jdk.incubator.vector.FloatVector;
|
||||||
|
import jdk.incubator.vector.VectorSpecies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vector implementation of magnitude calculation.
|
||||||
|
*/
|
||||||
|
public class VectorMagnitudeCalculator512 implements IMagnitudeCalculator
|
||||||
|
{
|
||||||
|
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] calculate(float[] i, float[] q)
|
||||||
|
{
|
||||||
|
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||||
|
float[] magnitude = new float[i.length];
|
||||||
|
|
||||||
|
FloatVector iVector, qVector, result;
|
||||||
|
for(int x = 0; x < i.length; x += VECTOR_SPECIES.length())
|
||||||
|
{
|
||||||
|
iVector = FloatVector.fromArray(VECTOR_SPECIES, i, x);
|
||||||
|
qVector = FloatVector.fromArray(VECTOR_SPECIES, q, x);
|
||||||
|
result = iVector.mul(iVector).add(qVector.mul(qVector));
|
||||||
|
result.intoArray(magnitude, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return magnitude;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.magnitude;
|
||||||
|
|
||||||
|
import io.github.dsheirer.vector.VectorUtilities;
|
||||||
|
import jdk.incubator.vector.FloatVector;
|
||||||
|
import jdk.incubator.vector.VectorSpecies;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vector implementation of magnitude calculation.
|
||||||
|
*/
|
||||||
|
public class VectorMagnitudeCalculator64 implements IMagnitudeCalculator
|
||||||
|
{
|
||||||
|
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float[] calculate(float[] i, float[] q)
|
||||||
|
{
|
||||||
|
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||||
|
float[] magnitude = new float[i.length];
|
||||||
|
|
||||||
|
FloatVector iVector, qVector, result;
|
||||||
|
for(int x = 0; x < i.length; x += VECTOR_SPECIES.length())
|
||||||
|
{
|
||||||
|
iVector = FloatVector.fromArray(VECTOR_SPECIES, i, x);
|
||||||
|
qVector = FloatVector.fromArray(VECTOR_SPECIES, q, x);
|
||||||
|
result = iVector.mul(iVector).add(qVector.mul(qVector));
|
||||||
|
result.intoArray(magnitude, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return magnitude;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,6 +30,7 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
|
|||||||
private IPhaseLockedLoop mPLL;
|
private IPhaseLockedLoop mPLL;
|
||||||
private Complex mReceivedSample = new Complex(0, 0);
|
private Complex mReceivedSample = new Complex(0, 0);
|
||||||
private Listener<T> mSymbolListener;
|
private Listener<T> mSymbolListener;
|
||||||
|
private boolean mRunning;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract Phase Shift Keyed (PSK) demodulator
|
* Abstract Phase Shift Keyed (PSK) demodulator
|
||||||
@@ -42,6 +43,31 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
|
|||||||
mPLL = phaseLockedLoop;
|
mPLL = phaseLockedLoop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts this decoder and sets the running flag to true
|
||||||
|
*/
|
||||||
|
public void start()
|
||||||
|
{
|
||||||
|
mRunning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops this decoder and sets the running flag to false.
|
||||||
|
*/
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
mRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this demodulator is running and capable of processing samples
|
||||||
|
* @return true if running
|
||||||
|
*/
|
||||||
|
public boolean isRunning()
|
||||||
|
{
|
||||||
|
return mRunning;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the listener to receive symbol decisions from this demodulator
|
* Registers the listener to receive symbol decisions from this demodulator
|
||||||
*/
|
*/
|
||||||
@@ -90,7 +116,10 @@ public abstract class PSKDemodulator<T> implements ComplexSampleListener
|
|||||||
|
|
||||||
for(int x = 0; x < i.length; x++)
|
for(int x = 0; x < i.length; x++)
|
||||||
{
|
{
|
||||||
receive(i[x], q[x]);
|
if(isRunning())
|
||||||
|
{
|
||||||
|
receive(i[x], q[x]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,324 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.squelch;
|
||||||
|
|
||||||
|
import io.github.dsheirer.dsp.filter.iir.SinglePoleIirFilter;
|
||||||
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
|
import java.time.Duration;
|
||||||
|
import org.apache.commons.math3.util.FastMath;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adaptive squelch control that optionally tracks the noise floor and auto-adjusts squelch threshold.
|
||||||
|
*
|
||||||
|
* Initially modeled on: https://github.com/gnuradio/gnuradio/blob/master/gr-analog/lib/simple_squelch_cc_impl.cc
|
||||||
|
*/
|
||||||
|
public class AdaptiveSquelch implements Listener<SourceEvent>
|
||||||
|
{
|
||||||
|
private static final Logger mLog = LoggerFactory.getLogger(AdaptiveSquelch.class);
|
||||||
|
private static final long AUTO_ADJUST_PERIOD_MS = Duration.ofSeconds(5).toMillis();
|
||||||
|
private SinglePoleIirFilter mFilter;
|
||||||
|
private float mPower = 0.0f;
|
||||||
|
private boolean mSquelch;
|
||||||
|
private float mSquelchThreshold;
|
||||||
|
private Float mNoiseFloor;
|
||||||
|
private float mSquelchBuffer;
|
||||||
|
private boolean mSquelchChanged = false;
|
||||||
|
private int mPowerLevelBroadcastCount = 0;
|
||||||
|
private int mPowerLevelBroadcastThreshold;
|
||||||
|
private boolean mAutoTrackNoiseFloor;
|
||||||
|
private long mLastAutoAdjustTimestamp;
|
||||||
|
private Listener<SourceEvent> mSourceEventListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* Testing against a 12.5 kHz analog FM modulated signal, the following parameters provided a
|
||||||
|
* good responsiveness. A threshold of -80.0 dB seemed to trigger significant flapping during un-squelching.
|
||||||
|
* - alpha: 0.0001
|
||||||
|
* - threshold: 78.0 dB
|
||||||
|
*
|
||||||
|
* @param alpha decay value of the single pole IIR filter in range: 0.0 - 1.0. The smaller the alpha value,
|
||||||
|
* the slower the squelch response.
|
||||||
|
* @param squelchThreshold in decibels. Signal power must exceed this threshold value for unsquelch.
|
||||||
|
* @param autoTrack the noise floor when squelched and periodically readjust the squelch threshold.
|
||||||
|
*/
|
||||||
|
public AdaptiveSquelch(float alpha, float squelchThreshold, boolean autoTrack)
|
||||||
|
{
|
||||||
|
mFilter = new SinglePoleIirFilter(alpha);
|
||||||
|
setSquelchThreshold(squelchThreshold);
|
||||||
|
mAutoTrackNoiseFloor = autoTrack;
|
||||||
|
mLastAutoAdjustTimestamp = System.currentTimeMillis();
|
||||||
|
mPowerLevelBroadcastThreshold = 25000; //Based on a default sample rate of 50 kHz, so 2x/second
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the sample rate to effect the frequency of power level notifications where the notifications are
|
||||||
|
* sent twice a second.
|
||||||
|
* @param sampleRate in hertz
|
||||||
|
*/
|
||||||
|
public void setSampleRate(int sampleRate)
|
||||||
|
{
|
||||||
|
mPowerLevelBroadcastThreshold = sampleRate / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Squelch threshold value
|
||||||
|
* @return value in decibels
|
||||||
|
*/
|
||||||
|
public float getSquelchThreshold()
|
||||||
|
{
|
||||||
|
return toDb(mSquelchThreshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the value as decibels
|
||||||
|
* @param value to format
|
||||||
|
* @return formatted value.
|
||||||
|
*/
|
||||||
|
private float toDb(float value)
|
||||||
|
{
|
||||||
|
return 10.0f * (float)FastMath.log10(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts from a decibel value
|
||||||
|
* @param valueDb in decibels
|
||||||
|
* @return not decibels
|
||||||
|
*/
|
||||||
|
private float fromDb(float valueDb)
|
||||||
|
{
|
||||||
|
return (float)FastMath.pow(10.0, valueDb / 10.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the squelch auto-tracking feature where the squelch threshold is auto-adjusted based on the
|
||||||
|
* observed channel power when the signal is not present (ie squelch state).
|
||||||
|
* @param autoTrack true to turn on auto-track
|
||||||
|
*/
|
||||||
|
public void setSquelchAutoTrack(boolean autoTrack)
|
||||||
|
{
|
||||||
|
mAutoTrackNoiseFloor = autoTrack;
|
||||||
|
broadcast(SourceEvent.squelchAutoTrack(getSquelchAutoTrack()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current squelch auto-track setting.
|
||||||
|
* @return true if auto-track is enabled.
|
||||||
|
*/
|
||||||
|
public boolean getSquelchAutoTrack()
|
||||||
|
{
|
||||||
|
return mAutoTrackNoiseFloor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the squelch threshold
|
||||||
|
* @param squelchThreshold in decibels
|
||||||
|
*/
|
||||||
|
public void setSquelchThreshold(float squelchThreshold)
|
||||||
|
{
|
||||||
|
float squelchThresholdDb = fromDb(squelchThreshold);
|
||||||
|
setSquelchThreshold(squelchThresholdDb, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the squelch threshold. This method is synchronized to coordinate changes requested by the user/UI thread
|
||||||
|
* and auto-track changes made by the stream processing thread.
|
||||||
|
*
|
||||||
|
* @param squelchThreshold value
|
||||||
|
* @param resetNoiseFloor to reset the noise floor and squelch threshold
|
||||||
|
*/
|
||||||
|
private synchronized void setSquelchThreshold(float squelchThreshold, boolean resetNoiseFloor)
|
||||||
|
{
|
||||||
|
mSquelchThreshold = squelchThreshold;
|
||||||
|
|
||||||
|
if(resetNoiseFloor)
|
||||||
|
{
|
||||||
|
//Set noise floor to null so that both the noise floor and the squelch buffer get updated after first power reading
|
||||||
|
mNoiseFloor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcast(SourceEvent.squelchThreshold(null, getSquelchThreshold()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the squelch state
|
||||||
|
* @param squelch true to squelch and false to unsquelch
|
||||||
|
*/
|
||||||
|
private void setSquelch(boolean squelch)
|
||||||
|
{
|
||||||
|
mSquelch = squelch;
|
||||||
|
mSquelchChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a complex IQ sample and changes squelch state when the signal power is above or below the
|
||||||
|
* threshold value.
|
||||||
|
* @param inphase complex sample component
|
||||||
|
* @param quadrature complex sample component
|
||||||
|
*/
|
||||||
|
public void process(float inphase, float quadrature)
|
||||||
|
{
|
||||||
|
process(inphase * inphase + quadrature * quadrature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes a complex IQ sample and changes squelch state when the signal power is above or below the
|
||||||
|
* threshold value.
|
||||||
|
* @param magnitude of a complex sample (inphase * inphase + quadrature * quadrature)
|
||||||
|
*/
|
||||||
|
public void process(float magnitude)
|
||||||
|
{
|
||||||
|
mPower = mFilter.filter(magnitude);
|
||||||
|
|
||||||
|
if(mSquelch && mPower >= mSquelchThreshold)
|
||||||
|
{
|
||||||
|
setSquelch(false);
|
||||||
|
}
|
||||||
|
else if(!mSquelch && mPower < mSquelchThreshold)
|
||||||
|
{
|
||||||
|
setSquelch(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
mPowerLevelBroadcastCount++;
|
||||||
|
if(mPowerLevelBroadcastCount % mPowerLevelBroadcastThreshold == 0)
|
||||||
|
{
|
||||||
|
mPowerLevelBroadcastCount = 0;
|
||||||
|
broadcast(SourceEvent.channelPowerLevel(null, 10.0 * Math.log10(mPower)));
|
||||||
|
|
||||||
|
if(mSquelch)
|
||||||
|
{
|
||||||
|
adjustSquelchThreshold();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With the user specified squelch threshold, establishes a squelch buffer (delta) from the actual noise floor and
|
||||||
|
* then automatically updates the squelch threshold to be the current noise floor plus this threshold value.
|
||||||
|
*
|
||||||
|
* Note: this method should only be invoked from a squelch (true) state.
|
||||||
|
*/
|
||||||
|
private void adjustSquelchThreshold()
|
||||||
|
{
|
||||||
|
if(mAutoTrackNoiseFloor)
|
||||||
|
{
|
||||||
|
if(mNoiseFloor == null)
|
||||||
|
{
|
||||||
|
//We set the squelch buffer just once, after the squelch threshold is set and noise floor is set to null
|
||||||
|
mSquelchBuffer = mSquelchThreshold - mPower;
|
||||||
|
}
|
||||||
|
|
||||||
|
mNoiseFloor = mPower;
|
||||||
|
|
||||||
|
if((System.currentTimeMillis() - mLastAutoAdjustTimestamp) > AUTO_ADJUST_PERIOD_MS)
|
||||||
|
{
|
||||||
|
setSquelchThreshold(mNoiseFloor + mSquelchBuffer, false);
|
||||||
|
mLastAutoAdjustTimestamp = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the current state is muted
|
||||||
|
*/
|
||||||
|
public boolean isMuted()
|
||||||
|
{
|
||||||
|
return mSquelch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the current state is unmuted
|
||||||
|
*/
|
||||||
|
public boolean isUnmuted()
|
||||||
|
{
|
||||||
|
return !mSquelch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current power level
|
||||||
|
* @return current power level in dB
|
||||||
|
*/
|
||||||
|
public float getPower()
|
||||||
|
{
|
||||||
|
return (float)(10.0 * Math.log10(mPower));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the squelch state has changed (muted > unmuted, or vice-versa)
|
||||||
|
*/
|
||||||
|
public boolean isSquelchChanged()
|
||||||
|
{
|
||||||
|
return mSquelchChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets or resets the squelch changed flag
|
||||||
|
*/
|
||||||
|
public void setSquelchChanged(boolean changed)
|
||||||
|
{
|
||||||
|
mSquelchChanged = changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive power level notifications and squelch threshold requests
|
||||||
|
*/
|
||||||
|
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||||
|
{
|
||||||
|
mSourceEventListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts the source event to an optional register listener
|
||||||
|
*/
|
||||||
|
private void broadcast(SourceEvent event)
|
||||||
|
{
|
||||||
|
if(mSourceEventListener != null)
|
||||||
|
{
|
||||||
|
mSourceEventListener.receive(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary method to receive requests for squelch threshold change
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void receive(SourceEvent sourceEvent)
|
||||||
|
{
|
||||||
|
switch(sourceEvent.getEvent())
|
||||||
|
{
|
||||||
|
case REQUEST_CHANGE_SQUELCH_THRESHOLD:
|
||||||
|
setSquelchThreshold(sourceEvent.getValue().floatValue());
|
||||||
|
break;
|
||||||
|
case REQUEST_CURRENT_SQUELCH_THRESHOLD:
|
||||||
|
broadcast(SourceEvent.squelchThreshold(null, getSquelchThreshold()));
|
||||||
|
break;
|
||||||
|
case REQUEST_CHANGE_SQUELCH_AUTO_TRACK:
|
||||||
|
setSquelchAutoTrack(sourceEvent.getValue().intValue() == 1 ? true : false);
|
||||||
|
break;
|
||||||
|
case REQUEST_CURRENT_SQUELCH_AUTO_TRACK:
|
||||||
|
broadcast(SourceEvent.squelchAutoTrack(getSquelchAutoTrack()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,4 +16,16 @@ public interface ISquelchConfiguration
|
|||||||
* @return threshold (dB)
|
* @return threshold (dB)
|
||||||
*/
|
*/
|
||||||
int getSquelchThreshold();
|
int getSquelchThreshold();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable or disable the squelch noise floor auto-track feature.
|
||||||
|
* @param autoTrack true to enable.
|
||||||
|
*/
|
||||||
|
void setSquelchAutoTrack(boolean autoTrack);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the squelch noise floor auto-track feature is enabled.
|
||||||
|
* @return true if enabled.
|
||||||
|
*/
|
||||||
|
boolean isSquelchAutoTrack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.squelch;
|
package io.github.dsheirer.dsp.squelch;
|
||||||
|
|
||||||
import io.github.dsheirer.dsp.filter.iir.SinglePoleIirFilter;
|
import io.github.dsheirer.dsp.filter.iir.SinglePoleIirFilter;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.source.SourceEvent;
|
import io.github.dsheirer.source.SourceEvent;
|
||||||
|
import org.apache.commons.math3.util.FastMath;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -17,8 +37,8 @@ public class PowerSquelch implements Listener<SourceEvent>
|
|||||||
private static final Logger mLog = LoggerFactory.getLogger(PowerSquelch.class);
|
private static final Logger mLog = LoggerFactory.getLogger(PowerSquelch.class);
|
||||||
private State mState = State.MUTE;
|
private State mState = State.MUTE;
|
||||||
private SinglePoleIirFilter mFilter;
|
private SinglePoleIirFilter mFilter;
|
||||||
private double mPower = 0.0f;
|
private float mPower = 0.0f;
|
||||||
private double mSquelchThreshold;
|
private float mSquelchThreshold;
|
||||||
private int mRampThreshold;
|
private int mRampThreshold;
|
||||||
private int mRampCount;
|
private int mRampCount;
|
||||||
private boolean mSquelchChanged = false;
|
private boolean mSquelchChanged = false;
|
||||||
@@ -42,7 +62,7 @@ public class PowerSquelch implements Listener<SourceEvent>
|
|||||||
* @param ramp count of samples before transition between mute and unmute. Setting this value to zero
|
* @param ramp count of samples before transition between mute and unmute. Setting this value to zero
|
||||||
* causes immediate mute and unmute. Set to higher count to prevent mute/unmute flapping.
|
* causes immediate mute and unmute. Set to higher count to prevent mute/unmute flapping.
|
||||||
*/
|
*/
|
||||||
public PowerSquelch(float alpha, double squelchThreshold, int ramp)
|
public PowerSquelch(float alpha, float squelchThreshold, int ramp)
|
||||||
{
|
{
|
||||||
mFilter = new SinglePoleIirFilter(alpha);
|
mFilter = new SinglePoleIirFilter(alpha);
|
||||||
setSquelchThreshold(squelchThreshold);
|
setSquelchThreshold(squelchThreshold);
|
||||||
@@ -64,9 +84,9 @@ public class PowerSquelch implements Listener<SourceEvent>
|
|||||||
* Squelch threshold value
|
* Squelch threshold value
|
||||||
* @return value in decibels
|
* @return value in decibels
|
||||||
*/
|
*/
|
||||||
public double getSquelchThreshold()
|
public float getSquelchThreshold()
|
||||||
{
|
{
|
||||||
return 10.0 * Math.log10(mSquelchThreshold);
|
return 10.0f * (float)FastMath.log10(mSquelchThreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,7 +95,7 @@ public class PowerSquelch implements Listener<SourceEvent>
|
|||||||
*/
|
*/
|
||||||
public void setSquelchThreshold(double squelchThreshold)
|
public void setSquelchThreshold(double squelchThreshold)
|
||||||
{
|
{
|
||||||
mSquelchThreshold = Math.pow(10.0, squelchThreshold / 10.0);
|
mSquelchThreshold = (float) FastMath.pow(10.0, squelchThreshold / 10.0);
|
||||||
broadcast(SourceEvent.squelchThreshold(null, squelchThreshold));
|
broadcast(SourceEvent.squelchThreshold(null, squelchThreshold));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +288,7 @@ public class PowerSquelch implements Listener<SourceEvent>
|
|||||||
{
|
{
|
||||||
if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD)
|
if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD)
|
||||||
{
|
{
|
||||||
setSquelchThreshold(sourceEvent.getValue().doubleValue());
|
setSquelchThreshold(sourceEvent.getValue().floatValue());
|
||||||
}
|
}
|
||||||
else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD)
|
else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD)
|
||||||
{
|
{
|
||||||
|
|||||||
125
src/main/java/io/github/dsheirer/edac/BPTC_16_2.java
Normal file
125
src/main/java/io/github/dsheirer/edac/BPTC_16_2.java
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.BinaryMessage;
|
||||||
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block Product Turbo Code 16/2 for decoding a DMR Voice Frame F payload from the 32-bit EMB field.
|
||||||
|
* <p>
|
||||||
|
* See ETSI TS 102 361-1 B.2.2.1 and B.2.2.2
|
||||||
|
*/
|
||||||
|
public class BPTC_16_2
|
||||||
|
{
|
||||||
|
private static final int[] DEINTERLEAVE = new int[]{0, 24, 1, 25, 2, 26, 3, 27, 4, 28, 5, 29, 6, 30, 7, 31, 8, 16,
|
||||||
|
9, 17, 10, 18, 11, 19, 12, 20, 13, 21, 14, 22, 15, 23};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unscramble and perform FEC checks per paragraph B.2.2.1 for Non-Reverse Channel Single Burst
|
||||||
|
*
|
||||||
|
* @param message with 32 interleaved bits.
|
||||||
|
* @return descrambled and error checked message or null if the process fails or there are too many errors.
|
||||||
|
*/
|
||||||
|
public static CorrectedBinaryMessage decodeShortBurst(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage deinterleaved = deinterleave(message);
|
||||||
|
int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
|
||||||
|
|
||||||
|
if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for even parity. Bits 0-15 should be the same as bits 16-31.
|
||||||
|
for(int x = 0; x < 16; x++)
|
||||||
|
{
|
||||||
|
if(deinterleaved.get(x) ^ deinterleaved.get(x + 16))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deinterleaved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unscramble and perform FEC checks per paragraph B.2.2.2 for Reverse Channel Single Burst
|
||||||
|
*
|
||||||
|
* @param binaryMessage with 32 interleaved bits.
|
||||||
|
* @return descrambled and error checked message or null if the process fails or there are too many errors.
|
||||||
|
*/
|
||||||
|
public static CorrectedBinaryMessage decodeReverseChannel(CorrectedBinaryMessage message)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage deinterleaved = deinterleave(message);
|
||||||
|
System.out.println(" DEINTER: " + deinterleaved.toHexString());
|
||||||
|
int fec = Hamming16.checkAndCorrect(deinterleaved, 0);
|
||||||
|
System.out.println(" DECODED: " + deinterleaved.toHexString());
|
||||||
|
System.out.println("FEC:" + fec);
|
||||||
|
if(fec == 2) //0 or 1 is good, 2 = uncorrectable errors
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for odd parity. Bits 0-15 should be opposite of bits 16-31.
|
||||||
|
for(int x = 0; x < 16; x++)
|
||||||
|
{
|
||||||
|
if(deinterleaved.get(x) == deinterleaved.get(x + 16))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deinterleaved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs deinterleave of the interleaved message.
|
||||||
|
*
|
||||||
|
* @param original to deinterleave
|
||||||
|
* @return deinterleaved message
|
||||||
|
*/
|
||||||
|
public static CorrectedBinaryMessage deinterleave(CorrectedBinaryMessage original)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage delinterleaved = new CorrectedBinaryMessage(32);
|
||||||
|
for(int x = 0; x < 32; x++)
|
||||||
|
{
|
||||||
|
if(original.get(x))
|
||||||
|
{
|
||||||
|
delinterleaved.set(DEINTERLEAVE[x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return delinterleaved;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
String[] msgs = new String[]{"05030A03", "35003A00", "1C6D2C9E"};
|
||||||
|
|
||||||
|
for(String msg : msgs)
|
||||||
|
{
|
||||||
|
CorrectedBinaryMessage original = new CorrectedBinaryMessage(BinaryMessage.loadHex(msg));
|
||||||
|
System.out.println("ORIGINAL: " + original.toHexString());
|
||||||
|
CorrectedBinaryMessage decoded = decodeReverseChannel(original);
|
||||||
|
System.out.println("-------------------------");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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;
|
package io.github.dsheirer.edac;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
import io.github.dsheirer.bits.BitSetFullException;
|
import io.github.dsheirer.bits.BitSetFullException;
|
||||||
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -14,7 +32,7 @@ import java.util.List;
|
|||||||
public class BPTC_196_96
|
public class BPTC_196_96
|
||||||
{
|
{
|
||||||
public static final int BPTC_LENGTH = 196;
|
public static final int BPTC_LENGTH = 196;
|
||||||
public static final int EXTRACTED_LENGTH = 96;
|
public static final int EXTRACTED_LENGTH = 96; //However, we set the 3x reserved bits in 96, 97, and 98 making the length 99
|
||||||
public static final int MAX_ORIGINAL_INDEX = 136;
|
public static final int MAX_ORIGINAL_INDEX = 136;
|
||||||
public static final int COLUMN_COUNT = 15;
|
public static final int COLUMN_COUNT = 15;
|
||||||
public static final int MESSAGE_COLUMN_COUNT = 12; //Should be 11, but adjusted for the first pad bit
|
public static final int MESSAGE_COLUMN_COUNT = 12; //Should be 11, but adjusted for the first pad bit
|
||||||
@@ -99,6 +117,11 @@ public class BPTC_196_96
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Transfer bits R2, R1, and R0 to the end - RAS bits
|
||||||
|
extracted.set(96, message.get(0));
|
||||||
|
extracted.set(97, message.get(1));
|
||||||
|
extracted.set(98, message.get(2));
|
||||||
|
|
||||||
return extracted;
|
return extracted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* SDR Trunk
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014 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
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
* ****************************************************************************
|
||||||
|
*/
|
||||||
package io.github.dsheirer.edac;
|
package io.github.dsheirer.edac;
|
||||||
|
|
||||||
import io.github.dsheirer.message.MessageDirection;
|
import io.github.dsheirer.message.MessageDirection;
|
||||||
|
|
||||||
import java.util.BitSet;
|
import java.util.BitSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,6 +98,14 @@ public class CRCLTR
|
|||||||
|
|
||||||
return CRC.FAILED_CRC;
|
return CRC.FAILED_CRC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getCRCReason(BitSet msg, MessageDirection direction)
|
||||||
|
{
|
||||||
|
int calculated = getCalculatedChecksum( msg );
|
||||||
|
int transmitted = getTransmittedChecksum( msg );
|
||||||
|
return "CALC: " + Integer.toHexString(calculated).toUpperCase() +
|
||||||
|
" TRANS: " + Integer.toHexString(transmitted).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
public static byte[] getChecks()
|
public static byte[] getChecks()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -226,17 +226,19 @@ public class CRCUtil
|
|||||||
{
|
{
|
||||||
mLog.debug("Starting");
|
mLog.debug("Starting");
|
||||||
|
|
||||||
//DMR message
|
long poly = 0x13l;
|
||||||
// String raw = "100110010001000000001001000000001111011011010000011001010000000000000000000000001100001000001011";
|
long[] checksums = generate(32, 4, poly, 0, true);
|
||||||
String raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101001001111010001110";
|
|
||||||
mLog.debug(raw);
|
|
||||||
raw = "101010000000000000110110100000101101011011010000011001111100000000000011100101000000000000000000";
|
|
||||||
BinaryMessage message = BinaryMessage.load(raw);
|
|
||||||
mLog.debug(message.toString());
|
|
||||||
|
|
||||||
long polynomial = 0x11021l;
|
StringBuilder sb = new StringBuilder();
|
||||||
decode(message, 0, 80, polynomial, 16);
|
sb.append("private static int[] CHECKSUMS = new int[]{");
|
||||||
mLog.debug(message.toString());
|
for(long checksum: checksums)
|
||||||
mLog.debug("Finished");
|
{
|
||||||
|
sb.append("0x").append(Long.toHexString(checksum).toUpperCase());
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("};");
|
||||||
|
|
||||||
|
System.out.println("Checksums:\n" + sb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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;
|
package io.github.dsheirer.edac;
|
||||||
|
|
||||||
import io.github.dsheirer.bits.BinaryMessage;
|
import io.github.dsheirer.bits.BinaryMessage;
|
||||||
@@ -5,27 +24,6 @@ import io.github.dsheirer.bits.CorrectedBinaryMessage;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* SDR Trunk
|
|
||||||
* Copyright (C) 2014 Dennis Sheirer
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* -----------------------------------------------------------------------
|
|
||||||
* Galois24 decoder based on Hank Wallace's tutorial/algorithm located at:
|
|
||||||
* http://www.aqdi.com/golay.htm
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Galois 24/12/7 decoder
|
* Galois 24/12/7 decoder
|
||||||
*/
|
*/
|
||||||
@@ -160,4 +158,17 @@ public class Golay24
|
|||||||
|
|
||||||
return (checksum ^ calculated);
|
return (checksum ^ calculated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F3BB20"));
|
||||||
|
// CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("F0C5C0"));
|
||||||
|
CorrectedBinaryMessage bm = new CorrectedBinaryMessage(BinaryMessage.loadHex("AFAC00"));
|
||||||
|
|
||||||
|
System.out.println("M:" + bm.toHexString());
|
||||||
|
int a = Golay24.checkAndCorrect(bm, 0);
|
||||||
|
System.out.println("M:" + bm.toHexString());
|
||||||
|
|
||||||
|
System.out.println("A:" + a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter;
|
package io.github.dsheirer.filter;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.function.Function;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a message filter that passes (true) all messages
|
* Implements an all-pass filter that automatically passes/allows any presented object.
|
||||||
|
*
|
||||||
|
* A key type of String is used, but that makes no difference in how this filter functions.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings( "rawtypes" )
|
public class AllPassFilter<T> extends Filter<T, String>
|
||||||
public class AllPassFilter<T> extends Filter<T>
|
|
||||||
{
|
{
|
||||||
public AllPassFilter()
|
private static final String KEY = "Other/Unlisted";
|
||||||
|
private final AllPassKeyExtractor mKeyExtractor = new AllPassKeyExtractor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param name to display for this filter
|
||||||
|
*/
|
||||||
|
public AllPassFilter(String name)
|
||||||
|
{
|
||||||
|
super(name);
|
||||||
|
add(new FilterElement<>(KEY));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Function getKeyExtractor()
|
||||||
{
|
{
|
||||||
super( "Allow All Messages" );
|
return mKeyExtractor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean passes( T t )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public boolean canProcess( T t )
|
* Key extractor that always returns the same (String)key.
|
||||||
{
|
*/
|
||||||
return true;
|
public class AllPassKeyExtractor implements Function<T,String>
|
||||||
}
|
{
|
||||||
|
@Override
|
||||||
@Override
|
public String apply(T t)
|
||||||
@SuppressWarnings( "unchecked" )
|
{
|
||||||
public List getFilterElements()
|
return KEY;
|
||||||
{
|
}
|
||||||
return Collections.EMPTY_LIST;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter;
|
package io.github.dsheirer.filter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public abstract class Filter<T> implements IFilter<T>
|
/**
|
||||||
|
* Base filter class that uses an extractor function to extract a filter key (K) from an object of type T and then
|
||||||
|
* lookup the filter element (rule) indicating if objects of this type are enabled.
|
||||||
|
*
|
||||||
|
* @param <T> object type that this filter can process.
|
||||||
|
* @param <K> key type used to lookup filter elements
|
||||||
|
*/
|
||||||
|
public abstract class Filter<T,K> implements IFilter<T>
|
||||||
{
|
{
|
||||||
protected String mName;
|
private static final Logger LOG = LoggerFactory.getLogger(Filter.class);
|
||||||
protected boolean mEnabled = true;
|
private String mName;
|
||||||
|
private Map<K, FilterElement<K>> mFilterElementMap = new HashMap<>();
|
||||||
public Filter( String name )
|
private IFilterChangeListener mFilterChangeListener;
|
||||||
{
|
|
||||||
mName = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this filter is enabled or disabled
|
* Constructs an instance
|
||||||
*/
|
* @param name of this filter
|
||||||
public boolean isEnabled()
|
*/
|
||||||
{
|
public Filter(String name)
|
||||||
return mEnabled;
|
{
|
||||||
}
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables (true) or disables (false) this filter. An enabled filter will
|
* Indicates if this filter contains a filter element matching the key value.
|
||||||
* evaluate messages and a disabled filter will return false on all message
|
* @param key to check
|
||||||
* test methods.
|
* @return true if this filter has a matching filter element.
|
||||||
*/
|
*/
|
||||||
public void setEnabled( boolean enabled )
|
protected boolean hasKey(K key)
|
||||||
{
|
{
|
||||||
mEnabled = enabled;
|
return mFilterElementMap.containsKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* List of filter elements managed by this filter
|
|
||||||
*/
|
|
||||||
public abstract List<FilterElement<?>> getFilterElements();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of this filter
|
* Key extractor function provided by the subclass.
|
||||||
*/
|
* @return key extractor function
|
||||||
public String getName()
|
*/
|
||||||
{
|
public abstract Function<T,K> getKeyExtractor();
|
||||||
return mName;
|
|
||||||
}
|
@Override
|
||||||
|
public boolean passes(T t)
|
||||||
public void setName( String name )
|
{
|
||||||
{
|
K key = getKeyExtractor().apply(t);
|
||||||
mName = name;
|
|
||||||
}
|
if(mFilterElementMap.containsKey(key))
|
||||||
|
{
|
||||||
public String toString()
|
return mFilterElementMap.get(key).isEnabled();
|
||||||
{
|
}
|
||||||
return mName;
|
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this filter can process the item T, meaning that it has a FilterElement that matches the key
|
||||||
|
* value for items of type T.
|
||||||
|
* @param t to test
|
||||||
|
* @return true if this filter can process item t
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean canProcess(T t)
|
||||||
|
{
|
||||||
|
return hasKey(getKeyExtractor().apply(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled()
|
||||||
|
{
|
||||||
|
for(FilterElement filterElement: mFilterElementMap.values())
|
||||||
|
{
|
||||||
|
if(filterElement.isEnabled())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the child filter element to this filter
|
||||||
|
* @param filterElement to add
|
||||||
|
*/
|
||||||
|
public void add(FilterElement<K> filterElement)
|
||||||
|
{
|
||||||
|
if(mFilterElementMap.containsKey(filterElement.getElement()))
|
||||||
|
{
|
||||||
|
LOG.warn("Filter element for key [" + filterElement.getElement() + "] named [" + filterElement.getName() +
|
||||||
|
"] already exists - overwriting existing value");
|
||||||
|
}
|
||||||
|
|
||||||
|
mFilterElementMap.put(filterElement.getElement(), filterElement);
|
||||||
|
filterElement.register(mFilterChangeListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of filter elements managed by this filter
|
||||||
|
*/
|
||||||
|
public final List<FilterElement<K>> getFilterElements()
|
||||||
|
{
|
||||||
|
return new ArrayList<>(mFilterElementMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of this filter
|
||||||
|
*/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the name of this filter
|
||||||
|
*
|
||||||
|
* @param name to set
|
||||||
|
*/
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pretty version or name of this filter.
|
||||||
|
*
|
||||||
|
* @return name
|
||||||
|
*/
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count of enabled child filter elements.
|
||||||
|
*
|
||||||
|
* @return count
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getEnabledCount()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for(FilterElement filterElement : mFilterElementMap.values())
|
||||||
|
{
|
||||||
|
if(filterElement.isEnabled())
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count of child filter elements
|
||||||
|
*
|
||||||
|
* @return count
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getElementCount()
|
||||||
|
{
|
||||||
|
return mFilterElementMap.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a filter change listener on this filter and all filter elements.
|
||||||
|
* @param listener to be notified of any filter changes.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void register(IFilterChangeListener listener)
|
||||||
|
{
|
||||||
|
mFilterChangeListener = listener;
|
||||||
|
|
||||||
|
for(FilterElement child: mFilterElementMap.values())
|
||||||
|
{
|
||||||
|
child.register(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
71
src/main/java/io/github/dsheirer/filter/FilterEditor.java
Normal file
71
src/main/java/io/github/dsheirer/filter/FilterEditor.java
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter editor
|
||||||
|
* @param <T> item type for editing
|
||||||
|
*/
|
||||||
|
public class FilterEditor<T> extends JFrame
|
||||||
|
{
|
||||||
|
private FilterEditorPanel<T> mEditorPanel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param title for the editor window frame
|
||||||
|
* @param owner to register the popup location
|
||||||
|
* @param filterSet to use initially
|
||||||
|
*/
|
||||||
|
public FilterEditor(String title, Component owner, FilterSet<T> filterSet)
|
||||||
|
{
|
||||||
|
if(filterSet == null)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Unable to construct FilterEditor - FilterSet cannot be null");
|
||||||
|
}
|
||||||
|
setTitle(title);
|
||||||
|
setSize(600, 400);
|
||||||
|
setLocationRelativeTo(owner);
|
||||||
|
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||||
|
setLayout(new MigLayout("", "[grow,fill]", "[grow,fill][]"));
|
||||||
|
mEditorPanel = new FilterEditorPanel<>(filterSet);
|
||||||
|
JScrollPane scroller = new JScrollPane(mEditorPanel);
|
||||||
|
scroller.setViewportView(mEditorPanel);
|
||||||
|
add(scroller, "wrap");
|
||||||
|
JButton close = new JButton("Close");
|
||||||
|
close.addActionListener(e -> dispose());
|
||||||
|
add(close);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates this editor with the filterset.
|
||||||
|
* @param filterSet to use in this editor.
|
||||||
|
*/
|
||||||
|
public void updateFilterSet(FilterSet<T> filterSet)
|
||||||
|
{
|
||||||
|
mEditorPanel.updateFilterSet(filterSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,100 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter;
|
package io.github.dsheirer.filter;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.EventObject;
|
||||||
|
import java.util.List;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
import javax.swing.JCheckBox;
|
import javax.swing.JCheckBox;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JMenuItem;
|
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JPopupMenu;
|
|
||||||
import javax.swing.JTree;
|
import javax.swing.JTree;
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import javax.swing.event.CellEditorListener;
|
import javax.swing.event.CellEditorListener;
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
import javax.swing.tree.DefaultTreeCellRenderer;
|
import javax.swing.tree.DefaultTreeCellRenderer;
|
||||||
import javax.swing.tree.DefaultTreeModel;
|
import javax.swing.tree.DefaultTreeModel;
|
||||||
import javax.swing.tree.TreeCellEditor;
|
import javax.swing.tree.TreeCellEditor;
|
||||||
|
import javax.swing.tree.TreeNode;
|
||||||
import javax.swing.tree.TreeSelectionModel;
|
import javax.swing.tree.TreeSelectionModel;
|
||||||
import java.awt.Component;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.awt.event.ItemEvent;
|
|
||||||
import java.awt.event.ItemListener;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.awt.event.MouseListener;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.EventObject;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Editor panel for managing the state of a filter set.
|
||||||
|
*
|
||||||
|
* @param <T> element type for the filter set.
|
||||||
|
*/
|
||||||
public class FilterEditorPanel<T> extends JPanel
|
public class FilterEditorPanel<T> extends JPanel
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private JTree mTree;
|
private JTree mTree;
|
||||||
private DefaultTreeModel mModel;
|
private DefaultTreeModel mModel;
|
||||||
private FilterSet<T> mFilterSet;
|
private FilterSet<T> mFilterSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param filterSet to manage
|
||||||
|
*/
|
||||||
public FilterEditorPanel(FilterSet<T> filterSet)
|
public FilterEditorPanel(FilterSet<T> filterSet)
|
||||||
{
|
{
|
||||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||||
|
|
||||||
mFilterSet = filterSet;
|
mFilterSet = filterSet;
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the panel and the tree model.
|
||||||
|
*/
|
||||||
private void init()
|
private void init()
|
||||||
{
|
{
|
||||||
DefaultMutableTreeNode root = new DefaultMutableTreeNode(mFilterSet);
|
DefaultMutableTreeNode root = new DefaultMutableTreeNode(mFilterSet);
|
||||||
|
|
||||||
mModel = new DefaultTreeModel(root);
|
mModel = new DefaultTreeModel(root);
|
||||||
|
|
||||||
addFilterSet(mFilterSet, root);
|
addFilterSet(mFilterSet, root);
|
||||||
|
|
||||||
mTree = new JTree(mModel);
|
mTree = new JTree(mModel);
|
||||||
mTree.setShowsRootHandles(true);
|
mTree.setShowsRootHandles(true);
|
||||||
|
mTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||||
mTree.addMouseListener(new MouseHandler());
|
|
||||||
|
|
||||||
mTree.getSelectionModel().setSelectionMode(
|
|
||||||
TreeSelectionModel.SINGLE_TREE_SELECTION);
|
|
||||||
|
|
||||||
mTree.setCellRenderer(new EditorTreeCellRenderer());
|
mTree.setCellRenderer(new EditorTreeCellRenderer());
|
||||||
|
mTree.setCellEditor(new FilterTreeCellEditor());
|
||||||
mTree.setCellEditor(new FilterTreeNodeEditor());
|
|
||||||
|
|
||||||
mTree.setEditable(true);
|
mTree.setEditable(true);
|
||||||
|
|
||||||
add(mTree);
|
add(mTree);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the filter set for this editor panel
|
||||||
|
* @param filterSet to use
|
||||||
|
*/
|
||||||
|
public void updateFilterSet(FilterSet<T> filterSet)
|
||||||
|
{
|
||||||
|
remove(mTree);
|
||||||
|
mFilterSet = filterSet;
|
||||||
|
init();
|
||||||
|
revalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the filter set as a child tree node to the parent.
|
||||||
|
* @param filterSet to add to the tree
|
||||||
|
* @param parent node for the filter set child tree node.
|
||||||
|
*/
|
||||||
private void addFilterSet(FilterSet<T> filterSet, DefaultMutableTreeNode parent)
|
private void addFilterSet(FilterSet<T> filterSet, DefaultMutableTreeNode parent)
|
||||||
{
|
{
|
||||||
List<IFilter<T>> filters = filterSet.getFilters();
|
List<IFilter<T>> filters = filterSet.getFilters();
|
||||||
|
|
||||||
/* sort the filters in alphabetical order by name */
|
|
||||||
filters.sort(Comparator.comparing(IFilter::getName));
|
|
||||||
|
|
||||||
for(IFilter<T> filter : filters)
|
for(IFilter<T> filter : filters)
|
||||||
{
|
{
|
||||||
DefaultMutableTreeNode child = new DefaultMutableTreeNode(filter);
|
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(filter);
|
||||||
|
mModel.insertNodeInto(childNode, parent, parent.getChildCount());
|
||||||
|
|
||||||
mModel.insertNodeInto(child, parent, parent.getChildCount());
|
if(filter instanceof FilterSet childFilterSet)
|
||||||
|
|
||||||
if(filter instanceof FilterSet)
|
|
||||||
{
|
{
|
||||||
addFilterSet((FilterSet<T>) filter, child);
|
addFilterSet(childFilterSet, childNode);
|
||||||
}
|
}
|
||||||
else if(filter instanceof Filter)
|
else if(filter instanceof Filter childFilter)
|
||||||
{
|
{
|
||||||
addFilter((Filter<T>) filter, child);
|
addFilter(childFilter, childNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addFilter(Filter<T> filter, DefaultMutableTreeNode parent)
|
/**
|
||||||
|
* Adds the filter as a child tree node to the parent.
|
||||||
|
*
|
||||||
|
* @param filter to add
|
||||||
|
* @param parent tree node
|
||||||
|
*/
|
||||||
|
private void addFilter(Filter filter, DefaultMutableTreeNode parent)
|
||||||
{
|
{
|
||||||
List<FilterElement<?>> elements = filter.getFilterElements();
|
List<FilterElement<?>> elements = filter.getFilterElements();
|
||||||
|
|
||||||
@@ -103,123 +131,39 @@ public class FilterEditorPanel<T> extends JPanel
|
|||||||
for(FilterElement<?> element : elements)
|
for(FilterElement<?> element : elements)
|
||||||
{
|
{
|
||||||
DefaultMutableTreeNode child = new DefaultMutableTreeNode(element);
|
DefaultMutableTreeNode child = new DefaultMutableTreeNode(element);
|
||||||
|
|
||||||
mModel.insertNodeInto(child, parent, parent.getChildCount());
|
mModel.insertNodeInto(child, parent, parent.getChildCount());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MouseHandler implements MouseListener
|
/**
|
||||||
|
* Accesses the tree's root tree node.
|
||||||
|
* @return root node.
|
||||||
|
*/
|
||||||
|
private DefaultMutableTreeNode getRoot()
|
||||||
{
|
{
|
||||||
public MouseHandler()
|
if(mTree.getModel().getRoot() instanceof DefaultMutableTreeNode root)
|
||||||
{
|
{
|
||||||
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
return null;
|
||||||
public void mouseClicked(MouseEvent event)
|
|
||||||
{
|
|
||||||
if(SwingUtilities.isRightMouseButton(event))
|
|
||||||
{
|
|
||||||
int row = mTree.getRowForLocation(event.getX(),
|
|
||||||
event.getY());
|
|
||||||
|
|
||||||
if(row != -1)
|
|
||||||
{
|
|
||||||
mTree.setSelectionRow(row);
|
|
||||||
|
|
||||||
Object selectedNode =
|
|
||||||
mTree.getLastSelectedPathComponent();
|
|
||||||
|
|
||||||
if(selectedNode instanceof DefaultMutableTreeNode)
|
|
||||||
{
|
|
||||||
final DefaultMutableTreeNode node =
|
|
||||||
(DefaultMutableTreeNode) selectedNode;
|
|
||||||
|
|
||||||
if(node.getChildCount() > 0)
|
|
||||||
{
|
|
||||||
JPopupMenu selectionMenu = new JPopupMenu();
|
|
||||||
|
|
||||||
JMenuItem selectAll = new JMenuItem("Select All");
|
|
||||||
selectAll.addActionListener(new ActionListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent arg0)
|
|
||||||
{
|
|
||||||
setFilterEnabled(mModel, node, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
selectionMenu.add(selectAll);
|
|
||||||
|
|
||||||
JMenuItem deselectAll = new JMenuItem("Deselect All");
|
|
||||||
deselectAll.addActionListener(new ActionListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent arg0)
|
|
||||||
{
|
|
||||||
setFilterEnabled(mModel, node, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
selectionMenu.add(deselectAll);
|
|
||||||
|
|
||||||
selectionMenu.show(mTree,
|
|
||||||
event.getX(),
|
|
||||||
event.getY());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mousePressed(MouseEvent e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseReleased(MouseEvent e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseEntered(MouseEvent e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseExited(MouseEvent e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively sets the enabled state on all filter node children below the
|
* Recursively sets the enabled state on all filter node children below the specified parent node.
|
||||||
* specified parent node.
|
|
||||||
*
|
*
|
||||||
* @param model - model containing the tree nodes
|
* @param model - model containing the tree nodes
|
||||||
* @param node - parent node
|
* @param node - parent node
|
||||||
* @param enabled - true or false to enable or disable the filters
|
* @param enabled - true or false to enable or disable the filters
|
||||||
*/
|
*/
|
||||||
private void setFilterEnabled(DefaultTreeModel model,
|
private void setFilterEnabled(DefaultTreeModel model, DefaultMutableTreeNode node, boolean enabled)
|
||||||
DefaultMutableTreeNode node,
|
|
||||||
boolean enabled)
|
|
||||||
{
|
{
|
||||||
Object obj = node.getUserObject();
|
Object obj = node.getUserObject();
|
||||||
|
|
||||||
if(obj instanceof FilterSet<?>)
|
if(obj instanceof FilterElement filterElement)
|
||||||
{
|
{
|
||||||
((FilterSet<?>) obj).setEnabled(enabled);
|
filterElement.setEnabled(enabled);
|
||||||
}
|
}
|
||||||
else if(obj instanceof Filter)
|
|
||||||
{
|
|
||||||
((Filter<?>) obj).setEnabled(enabled);
|
|
||||||
}
|
|
||||||
else if(obj instanceof FilterElement)
|
|
||||||
{
|
|
||||||
((FilterElement<?>) obj).setEnabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
model.nodeChanged(node);
|
|
||||||
|
|
||||||
/* Recursively set the children of this node */
|
/* Recursively set the children of this node */
|
||||||
Enumeration<?> children = node.children();
|
Enumeration<?> children = node.children();
|
||||||
@@ -228,43 +172,123 @@ public class FilterEditorPanel<T> extends JPanel
|
|||||||
{
|
{
|
||||||
Object child = children.nextElement();
|
Object child = children.nextElement();
|
||||||
|
|
||||||
if(child instanceof DefaultMutableTreeNode)
|
if(child instanceof DefaultMutableTreeNode childNode)
|
||||||
{
|
{
|
||||||
setFilterEnabled(model, (DefaultMutableTreeNode) child, enabled);
|
setFilterEnabled(model, childNode, enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model.nodeChanged(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively searches the tree to find the tree node containing the specified filter element.
|
||||||
|
*
|
||||||
|
* @param node to start search
|
||||||
|
* @param element to find
|
||||||
|
*/
|
||||||
|
private DefaultMutableTreeNode findFilterElementNode(DefaultMutableTreeNode node, FilterElement<?> element)
|
||||||
|
{
|
||||||
|
if(node != null)
|
||||||
|
{
|
||||||
|
if(node.getUserObject() instanceof FilterElement<?> selfFilterElement && selfFilterElement.equals(element))
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int x = 0; x < node.getChildCount(); x++)
|
||||||
|
{
|
||||||
|
if(node.getChildAt(x) instanceof DefaultMutableTreeNode child)
|
||||||
|
{
|
||||||
|
DefaultMutableTreeNode found = findFilterElementNode(child, element);
|
||||||
|
|
||||||
|
if(found != null)
|
||||||
|
{
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the display value for the specified node and all parents in the nodes parentage.
|
||||||
|
* @param node to update
|
||||||
|
*/
|
||||||
|
private void updateParentage(DefaultMutableTreeNode node)
|
||||||
|
{
|
||||||
|
mModel.nodeChanged(node);
|
||||||
|
|
||||||
|
TreeNode parent = node.getParent();
|
||||||
|
|
||||||
|
while(parent != null)
|
||||||
|
{
|
||||||
|
mModel.nodeChanged(parent);
|
||||||
|
parent = parent.getParent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively searches the tree to find the tree node containing the specified filter.
|
||||||
|
*
|
||||||
|
* @param node to start search
|
||||||
|
* @param filter to find
|
||||||
|
*/
|
||||||
|
private DefaultMutableTreeNode findFilterNode(DefaultMutableTreeNode node, IFilter<?> filter)
|
||||||
|
{
|
||||||
|
if(node != null)
|
||||||
|
{
|
||||||
|
if(node.getUserObject() instanceof IFilter<?> selfFilter && selfFilter.equals(filter))
|
||||||
|
{
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int x = 0; x < node.getChildCount(); x++)
|
||||||
|
{
|
||||||
|
if(node.getChildAt(x) instanceof DefaultMutableTreeNode child)
|
||||||
|
{
|
||||||
|
DefaultMutableTreeNode found = findFilterNode(child, filter);
|
||||||
|
|
||||||
|
if(found != null)
|
||||||
|
{
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.out.println("Can't evaluate - node is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom cell renderer
|
||||||
|
*/
|
||||||
public class EditorTreeCellRenderer extends DefaultTreeCellRenderer
|
public class EditorTreeCellRenderer extends DefaultTreeCellRenderer
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
public Component getTreeCellRendererComponent(JTree tree,
|
public Component getTreeCellRendererComponent(JTree tree, Object treeNode, boolean selected, boolean expanded,
|
||||||
Object treeNode,
|
boolean leaf, int row, boolean hasFocus)
|
||||||
boolean selected,
|
|
||||||
boolean expanded,
|
|
||||||
boolean leaf,
|
|
||||||
int row,
|
|
||||||
boolean hasFocus)
|
|
||||||
{
|
{
|
||||||
if(treeNode instanceof DefaultMutableTreeNode)
|
if(treeNode instanceof DefaultMutableTreeNode)
|
||||||
{
|
{
|
||||||
Object userObject = ((DefaultMutableTreeNode) treeNode).getUserObject();
|
Object userObject = ((DefaultMutableTreeNode) treeNode).getUserObject();
|
||||||
|
|
||||||
JCheckBox checkBox = null;
|
JCheckBox checkBox = null;
|
||||||
|
|
||||||
if(userObject instanceof IFilter)
|
if(userObject instanceof IFilter filter)
|
||||||
{
|
{
|
||||||
IFilter<?> filter = (IFilter<?>) userObject;
|
checkBox = new FilterCheckBox(filter);
|
||||||
|
|
||||||
checkBox = new JCheckBox(filter.getName());
|
|
||||||
checkBox.setSelected(filter.isEnabled());
|
checkBox.setSelected(filter.isEnabled());
|
||||||
}
|
}
|
||||||
else if(userObject instanceof FilterElement)
|
else if(userObject instanceof FilterElement element)
|
||||||
{
|
{
|
||||||
FilterElement<?> element = (FilterElement<?>) userObject;
|
checkBox = new FilterElementCheckBox(element);
|
||||||
|
|
||||||
checkBox = new JCheckBox(element.getName());
|
|
||||||
checkBox.setSelected(element.isEnabled());
|
checkBox.setSelected(element.isEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,95 +309,110 @@ public class FilterEditorPanel<T> extends JPanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.getTreeCellRendererComponent(tree, treeNode, selected,
|
return super.getTreeCellRendererComponent(tree, treeNode, selected, expanded, leaf, row, hasFocus);
|
||||||
expanded, leaf, row, hasFocus);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check box used in filter tree nodes
|
||||||
|
*/
|
||||||
public class FilterCheckBox extends JCheckBox
|
public class FilterCheckBox extends JCheckBox
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
private IFilter<?> mFilter;
|
||||||
|
|
||||||
private IFilter<T> mFilter;
|
/**
|
||||||
|
* Constructs an instance
|
||||||
public FilterCheckBox(IFilter<T> filter)
|
*
|
||||||
|
* @param filter
|
||||||
|
*/
|
||||||
|
public FilterCheckBox(IFilter<?> filter)
|
||||||
{
|
{
|
||||||
super(filter.getName());
|
|
||||||
|
|
||||||
mFilter = filter;
|
mFilter = filter;
|
||||||
|
|
||||||
setSelected(mFilter.isEnabled());
|
setSelected(mFilter.isEnabled());
|
||||||
|
addItemListener(e -> {
|
||||||
|
DefaultMutableTreeNode selfNode = findFilterNode(getRoot(), mFilter);
|
||||||
|
|
||||||
addItemListener(new ItemListener()
|
if(selfNode != null)
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void itemStateChanged(ItemEvent e)
|
|
||||||
{
|
{
|
||||||
mFilter.setEnabled(FilterCheckBox.this.isSelected());
|
setFilterEnabled(mModel, selfNode, FilterCheckBox.this.isSelected());
|
||||||
|
updateLabel();
|
||||||
|
updateParentage(selfNode);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSelected(boolean b)
|
||||||
|
{
|
||||||
|
super.setSelected(b);
|
||||||
|
updateLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateLabel()
|
||||||
|
{
|
||||||
|
setText(mFilter.getName() + " (" + mFilter.getEnabledCount() + "/" + mFilter.getElementCount() + ")");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check box used in filter element tree nodes
|
||||||
|
*/
|
||||||
public class FilterElementCheckBox extends JCheckBox
|
public class FilterElementCheckBox extends JCheckBox
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private FilterElement<?> mFilter;
|
private FilterElement<?> mFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param filter element
|
||||||
|
*/
|
||||||
public FilterElementCheckBox(FilterElement<?> filter)
|
public FilterElementCheckBox(FilterElement<?> filter)
|
||||||
{
|
{
|
||||||
super(filter.getName());
|
super(filter.getName());
|
||||||
|
|
||||||
mFilter = filter;
|
mFilter = filter;
|
||||||
|
|
||||||
setSelected(mFilter.isEnabled());
|
setSelected(mFilter.isEnabled());
|
||||||
|
addItemListener(arg0 -> {
|
||||||
addItemListener(new ItemListener()
|
mFilter.setEnabled(FilterElementCheckBox.this.isSelected());
|
||||||
{
|
DefaultMutableTreeNode self = findFilterElementNode(getRoot(), mFilter);
|
||||||
@Override
|
if(self != null)
|
||||||
public void itemStateChanged(ItemEvent arg0)
|
|
||||||
{
|
{
|
||||||
mFilter.setEnabled(FilterElementCheckBox.this.isSelected());
|
updateParentage(self);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FilterTreeNodeEditor implements TreeCellEditor
|
/**
|
||||||
|
* Editor for filter tree nodes
|
||||||
|
*/
|
||||||
|
public class FilterTreeCellEditor implements TreeCellEditor
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
public Component getTreeCellEditorComponent(JTree tree, Object node, boolean isSelected, boolean expanded, boolean leaf, int row)
|
||||||
public Component getTreeCellEditorComponent(JTree tree, Object node,
|
|
||||||
boolean isSelected, boolean expanded, boolean leaf, int row)
|
|
||||||
{
|
{
|
||||||
if(node instanceof DefaultMutableTreeNode)
|
if(node instanceof DefaultMutableTreeNode mutableTreeNode)
|
||||||
{
|
{
|
||||||
Object userObject = ((DefaultMutableTreeNode) node).getUserObject();
|
Object userObject = mutableTreeNode.getUserObject();
|
||||||
|
|
||||||
if(userObject instanceof IFilter)
|
if(userObject instanceof IFilter filter)
|
||||||
{
|
{
|
||||||
return new FilterCheckBox((IFilter) userObject);
|
return new FilterCheckBox(filter);
|
||||||
}
|
}
|
||||||
else if(userObject instanceof FilterElement)
|
else if(userObject instanceof FilterElement filterElement)
|
||||||
{
|
{
|
||||||
return new FilterElementCheckBox((FilterElement<?>) userObject);
|
return new FilterElementCheckBox(filterElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new JLabel(node.toString());
|
return new JLabel(node.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addCellEditorListener(CellEditorListener l)
|
public void addCellEditorListener(CellEditorListener l) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancelCellEditing()
|
public void cancelCellEditing() {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getCellEditorValue()
|
public Object getCellEditorValue()
|
||||||
@@ -388,9 +427,7 @@ public class FilterEditorPanel<T> extends JPanel
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeCellEditorListener(CellEditorListener l)
|
public void removeCellEditorListener(CellEditorListener l) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldSelectCell(EventObject anEvent)
|
public boolean shouldSelectCell(EventObject anEvent)
|
||||||
|
|||||||
@@ -1,55 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter;
|
package io.github.dsheirer.filter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter element provides filtering and enabled state tracking for an individual filterable element.
|
||||||
|
*
|
||||||
|
* @param <T> element to track and filter.
|
||||||
|
*/
|
||||||
public class FilterElement<T> implements Comparable<FilterElement<T>>
|
public class FilterElement<T> implements Comparable<FilterElement<T>>
|
||||||
{
|
{
|
||||||
private boolean mEnabled;
|
private boolean mEnabled;
|
||||||
private T mElement;
|
private final T mElement;
|
||||||
|
private IFilterChangeListener mFilterChangeListener;
|
||||||
public FilterElement( T element, boolean enabled )
|
|
||||||
{
|
|
||||||
mElement = element;
|
|
||||||
mEnabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilterElement( T t )
|
|
||||||
{
|
|
||||||
this( t, true );
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getElement()
|
|
||||||
{
|
|
||||||
return mElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEnabled()
|
|
||||||
{
|
|
||||||
return mEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEnabled( boolean enabled )
|
|
||||||
{
|
|
||||||
mEnabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName()
|
|
||||||
{
|
|
||||||
return mElement.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public int compareTo( FilterElement<T> other )
|
* Constructs an instance
|
||||||
|
*
|
||||||
|
* @param element to track
|
||||||
|
* @param enabled enabled state of this filter element
|
||||||
|
*/
|
||||||
|
public FilterElement(T element, boolean enabled)
|
||||||
{
|
{
|
||||||
return toString().compareTo( other.toString() );
|
mElement = element;
|
||||||
|
mEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance with a default state of enabled (true).
|
||||||
|
*
|
||||||
|
* @param t element to track
|
||||||
|
*/
|
||||||
|
public FilterElement(T t)
|
||||||
|
{
|
||||||
|
this(t, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener with each child filter element.
|
||||||
|
*
|
||||||
|
* @param listener to register
|
||||||
|
*/
|
||||||
|
public void register(IFilterChangeListener listener)
|
||||||
|
{
|
||||||
|
mFilterChangeListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element being filtered.
|
||||||
|
*
|
||||||
|
* @return element
|
||||||
|
*/
|
||||||
|
public T getElement()
|
||||||
|
{
|
||||||
|
return mElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name or display string for the element.
|
||||||
|
*
|
||||||
|
* @return string version of the element.
|
||||||
|
*/
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this filter is enabled indicating that elements of this type will pass this filter.
|
||||||
|
*
|
||||||
|
* @return enabled state.
|
||||||
|
*/
|
||||||
|
public boolean isEnabled()
|
||||||
|
{
|
||||||
|
return mEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the enabled state of this filter.
|
||||||
|
*
|
||||||
|
* @param enabled true to allow this filter's element type to pass the filter.
|
||||||
|
*/
|
||||||
|
public void setEnabled(boolean enabled)
|
||||||
|
{
|
||||||
|
mEnabled = enabled;
|
||||||
|
|
||||||
|
if(mFilterChangeListener != null)
|
||||||
|
{
|
||||||
|
mFilterChangeListener.filterChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the tracked element.
|
||||||
|
*
|
||||||
|
* @return name
|
||||||
|
*/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return mElement.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements compareto interface for ordering of filter elements.
|
||||||
|
*
|
||||||
|
* @param other the object to be compared.
|
||||||
|
* @return comparison value
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public int compareTo(FilterElement<T> other)
|
||||||
if (!(o instanceof FilterElement)) return false;
|
{
|
||||||
return compareTo((FilterElement) o) == 0;
|
return toString().compareTo(other.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implement equals interface for comparison of filter elements.
|
||||||
|
*
|
||||||
|
* @param o to compare
|
||||||
|
* @return comparison
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o)
|
||||||
|
{
|
||||||
|
if(o instanceof FilterElement fe)
|
||||||
|
{
|
||||||
|
return compareTo(fe) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,108 +1,239 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter;
|
package io.github.dsheirer.filter;
|
||||||
|
|
||||||
|
import io.github.dsheirer.log.LoggingSuppressor;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter set is a parent container for filters.
|
||||||
|
*
|
||||||
|
* @param <T> type of element tracked/filtered by this filter set.
|
||||||
|
*/
|
||||||
public class FilterSet<T> implements IFilter<T>
|
public class FilterSet<T> implements IFilter<T>
|
||||||
{
|
{
|
||||||
protected List<IFilter<T>> mFilters = new ArrayList<IFilter<T>>();
|
private static final Logger LOGGER = LoggerFactory.getLogger(FilterSet.class);
|
||||||
protected boolean mEnabled = true;
|
private static LoggingSuppressor sLoggingSuppressor = new LoggingSuppressor(LOGGER);
|
||||||
protected String mName;
|
private List<IFilter<T>> mFilters = new ArrayList<>();
|
||||||
|
private String mName;
|
||||||
|
private IFilterChangeListener mFilterChangeListener;
|
||||||
|
|
||||||
public FilterSet( String name )
|
/**
|
||||||
{
|
* Constructs an instance of a filter set.
|
||||||
mName = name;
|
*
|
||||||
}
|
* @param name of this filter set.
|
||||||
|
*/
|
||||||
public FilterSet()
|
public FilterSet(String name)
|
||||||
{
|
|
||||||
mName = "Message Filter";
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilterSet(IFilter<T> filter)
|
|
||||||
{
|
{
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance with a single filter using the default 'Filters' name.
|
||||||
|
*
|
||||||
|
* @param filter for this filter set.
|
||||||
|
*/
|
||||||
|
public FilterSet(IFilter<T> filter)
|
||||||
|
{
|
||||||
|
this("Filters");
|
||||||
addFilter(filter);
|
addFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName()
|
|
||||||
{
|
|
||||||
return mName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName( String name )
|
|
||||||
{
|
|
||||||
mName = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean passes( T t )
|
|
||||||
{
|
|
||||||
if( mEnabled )
|
|
||||||
{
|
|
||||||
for( IFilter<T> filter: mFilters )
|
|
||||||
{
|
|
||||||
// Make sure to loop through all filters.
|
|
||||||
// Only return if true or all filters have been evaluated.
|
|
||||||
if( filter.canProcess( t ) && filter.passes( t ))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public boolean canProcess( T t )
|
* Registers the listener with each child filter element.
|
||||||
{
|
* @param listener to register
|
||||||
if( mEnabled )
|
*/
|
||||||
|
public void register(IFilterChangeListener listener)
|
||||||
|
{
|
||||||
|
mFilterChangeListener = listener;
|
||||||
|
|
||||||
|
for(IFilter child: mFilters)
|
||||||
{
|
{
|
||||||
for( IFilter<T> filter: mFilters )
|
child.register(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of this filter set.
|
||||||
|
*
|
||||||
|
* @return name
|
||||||
|
*/
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the name of this filter set.
|
||||||
|
*
|
||||||
|
* @param name to set
|
||||||
|
*/
|
||||||
|
public void setName(String name)
|
||||||
|
{
|
||||||
|
mName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name for this filter set.
|
||||||
|
*
|
||||||
|
* @return name
|
||||||
|
*/
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the element passes at least one of the child filters in this filter set.
|
||||||
|
*
|
||||||
|
* @param t to evaluate
|
||||||
|
* @return true if the argument passes a child filter.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean passes(T t)
|
||||||
|
{
|
||||||
|
for(IFilter<T> filter : mFilters)
|
||||||
|
{
|
||||||
|
//Stop at the first filter that says that it can process the message type.
|
||||||
|
if(filter.canProcess(t))
|
||||||
{
|
{
|
||||||
if( filter.canProcess( t ) )
|
return filter.passes(t);
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this filter set contains at least one filter that can evaluate the type of argument that is presented.
|
||||||
|
*
|
||||||
|
* @param t element to evaluate
|
||||||
|
* @return true if this filter set can evaluate objects of the argument's type.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean canProcess(T t)
|
||||||
|
{
|
||||||
|
for(IFilter<T> filter : mFilters)
|
||||||
|
{
|
||||||
|
if(filter.canProcess(t))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sLoggingSuppressor.info(t.getClass().getSimpleName(), 1, "FilterSet [" +
|
||||||
|
this.getClass().getSimpleName() + "] has no filter element for items of type [" +
|
||||||
|
t.getClass().getSimpleName() + "]");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Child filters
|
||||||
|
*
|
||||||
|
* @return filters
|
||||||
|
*/
|
||||||
public List<IFilter<T>> getFilters()
|
public List<IFilter<T>> getFilters()
|
||||||
{
|
{
|
||||||
return mFilters;
|
return new ArrayList<>(mFilters);
|
||||||
}
|
|
||||||
|
|
||||||
public void addFilters( List<IFilter<T>> filters )
|
|
||||||
{
|
|
||||||
mFilters.addAll( filters );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addFilter( IFilter<T> filter )
|
|
||||||
{
|
|
||||||
mFilters.add( filter );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeFilter( IFilter<T> filter )
|
|
||||||
{
|
|
||||||
mFilters.remove( filter );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public boolean isEnabled()
|
* Adds a list of child filters to this filter set.
|
||||||
{
|
* @param filters to add
|
||||||
return mEnabled;
|
*/
|
||||||
}
|
public void addFilters(List<IFilter<T>> filters)
|
||||||
|
{
|
||||||
|
for(IFilter<T> filter: filters)
|
||||||
|
{
|
||||||
|
addFilter(filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the child filter to this filter set.
|
||||||
|
* @param filter to add
|
||||||
|
*/
|
||||||
|
public void addFilter(IFilter<T> filter)
|
||||||
|
{
|
||||||
|
if(filter instanceof Filter<?,?> filterInstance && filterInstance.getKeyExtractor() == null)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Filter [" + filter.getClass() + "] has a null key extractor");
|
||||||
|
}
|
||||||
|
|
||||||
|
mFilters.add(filter);
|
||||||
|
filter.register(mFilterChangeListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if any of the child filters contain at least one filter element that is enabled.
|
||||||
|
* @return enabled state
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setEnabled( boolean enabled )
|
public boolean isEnabled()
|
||||||
{
|
{
|
||||||
mEnabled = enabled;
|
for(IFilter filter: mFilters)
|
||||||
}
|
{
|
||||||
|
if(filter.isEnabled())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive count of enabled child filter elements.
|
||||||
|
* @return count
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getEnabledCount()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for(IFilter filter: mFilters)
|
||||||
|
{
|
||||||
|
count += filter.getEnabledCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive count of child filter elements.
|
||||||
|
* @return count
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getElementCount()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for(IFilter filter: mFilters)
|
||||||
|
{
|
||||||
|
count += filter.getElementCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* 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.filter;
|
package io.github.dsheirer.filter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter interface
|
||||||
|
*
|
||||||
|
* @param <T> type of filter
|
||||||
|
*/
|
||||||
public interface IFilter<T>
|
public interface IFilter<T>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Generic filter method.
|
* Generic filter method.
|
||||||
*
|
*
|
||||||
* @param message - message to filter
|
* @param t - message to filter
|
||||||
* @return - true if the message passes the filter
|
* @return - true if the message passes the filter
|
||||||
*/
|
*/
|
||||||
public boolean passes( T t );
|
boolean passes(T t);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the filter can process (filter) the object
|
* Indicates if the filter can process (filter) the object
|
||||||
*
|
*
|
||||||
* @param message - candidate message for filtering
|
* @param t - candidate message for filtering
|
||||||
*
|
* @return - true if the filter is capable of filtering the message
|
||||||
* @return - true if the filter is capable of filtering the message
|
*/
|
||||||
*/
|
boolean canProcess(T t);
|
||||||
public boolean canProcess( T t );
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if this filter is enabled to evaluate messages
|
* Indicates if this filter is enabled to evaluate messages
|
||||||
*/
|
*/
|
||||||
public boolean isEnabled();
|
boolean isEnabled();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the enabled state of the filter
|
* Display name for the filter
|
||||||
*/
|
*/
|
||||||
public void setEnabled( boolean enabled );
|
String getName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display name for the filter
|
* (Recursive) count of enabled child filter elements.
|
||||||
*/
|
*
|
||||||
public String getName();
|
* @return total enable count.
|
||||||
|
*/
|
||||||
|
int getEnabledCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Recursive) count of total child filter elements.
|
||||||
|
*
|
||||||
|
* @return total count of child filter elements.
|
||||||
|
*/
|
||||||
|
int getElementCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to be notified when the enabled state of any child filter elements is updated.
|
||||||
|
*
|
||||||
|
* @param listener to be notified
|
||||||
|
*/
|
||||||
|
void register(IFilterChangeListener listener);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user