mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 15:17:24 -05:00
Compare commits
32 Commits
2466-gradl
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02c62d012f | ||
|
|
a4ec04a499 | ||
|
|
006e382b34 | ||
|
|
beb2593dd9 | ||
|
|
657cbf5c55 | ||
|
|
aff34fa5eb | ||
|
|
22cd8968cb | ||
|
|
ec7efa4d17 | ||
|
|
07fd8c2923 | ||
|
|
a7301e93d9 | ||
|
|
5912a958b3 | ||
|
|
9ca4ae4d8f | ||
|
|
6cd2cea30b | ||
|
|
ba16c8b377 | ||
|
|
d72c5d87be | ||
|
|
dea3a3a41e | ||
|
|
48296990e9 | ||
|
|
289150d415 | ||
|
|
69d49d9dd0 | ||
|
|
e8e813142b | ||
|
|
968705a589 | ||
|
|
1c097491d9 | ||
|
|
0b3ce66cad | ||
|
|
14c77c4926 | ||
|
|
8aadbe4706 | ||
|
|
19d18b96b3 | ||
|
|
59e64a73ff | ||
|
|
8b338dec2d | ||
|
|
bab02222a4 | ||
|
|
9359f523c1 | ||
|
|
e385d2fdec | ||
|
|
49c3d3ff55 |
10
.github/workflows/gradle.yml
vendored
10
.github/workflows/gradle.yml
vendored
@@ -21,14 +21,14 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
- name: Set up JDK 27
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v6
|
||||
with:
|
||||
java-version: '27'
|
||||
distribution: 'liberica'
|
||||
java-package: 'jdk+fx'
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
- name: Build with Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
arguments: clean build
|
||||
run: ./gradlew clean build
|
||||
|
||||
17
.github/workflows/nightly.yml
vendored
17
.github/workflows/nightly.yml
vendored
@@ -13,32 +13,31 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Set up JDK 27
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v6
|
||||
with:
|
||||
# Must match the JDK version downloaded in build.gradle (jdk_download_base) - jlink requires an exact version match
|
||||
java-version: '27.0.0'
|
||||
distribution: 'liberica'
|
||||
java-package: 'jdk+fx'
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
|
||||
- name: Build Linux and Mac releases with Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
arguments: runtimeZipOthers -PprojectVersion=nightly
|
||||
run: ./gradlew runtimeZipOthers -PprojectVersion=nightly
|
||||
|
||||
- name: Build Windows release with Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
arguments: runtimeZipWindows -PprojectVersion=nightly
|
||||
run: ./gradlew 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: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
tag_name: nightly
|
||||
prerelease: true
|
||||
|
||||
130
.github/workflows/release.yml
vendored
Normal file
130
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
# Publishes a release when a version tag is pushed. The tag has to match
|
||||
# projectVersion in gradle.properties with a leading 'v': v0.6.2 for a final
|
||||
# release, or v0.6.2-beta-1 / v0.6.2-alpha-1 for a pre-release.
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v[0-9]+.[0-9]+.[0-9]+*']
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
previous_tag: ${{ steps.previous.outputs.tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
# Full history and tags, to find the previous release tag for the release notes
|
||||
fetch-depth: 0
|
||||
|
||||
- id: version
|
||||
run: |
|
||||
expected="v$(sed -n 's/^projectVersion=//p' gradle.properties | tr -d '[:space:]')"
|
||||
if [ "$GITHUB_REF_NAME" != "$expected" ]; then
|
||||
echo "::error::Tag $GITHUB_REF_NAME does not match projectVersion in gradle.properties ($expected)"
|
||||
exit 1
|
||||
fi
|
||||
echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Release notes list the PRs since the previous version tag (never 'nightly'). A final release
|
||||
# starts from the previous final release, so it covers every alpha and beta in between.
|
||||
- id: previous
|
||||
run: |
|
||||
exclude=(--exclude "$GITHUB_REF_NAME")
|
||||
if [[ "$GITHUB_REF_NAME" != *-* ]]; then
|
||||
exclude+=(--exclude '*-*')
|
||||
fi
|
||||
tag="$(git describe --tags --abbrev=0 --match 'v[0-9]*' "${exclude[@]}" HEAD 2>/dev/null || true)"
|
||||
echo "Release notes start from: ${tag:-(GitHub default)}"
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up JDK 27
|
||||
uses: actions/setup-java@v6
|
||||
with:
|
||||
# Must match the JDK version downloaded in build.gradle (jdk_download_base) - jlink requires an exact version match
|
||||
java-version: '27.0.0'
|
||||
distribution: 'liberica'
|
||||
java-package: 'jdk+fx'
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
|
||||
- name: Build and test
|
||||
run: ./gradlew clean build --console=plain
|
||||
|
||||
# The runtime plugin can't build Windows and the other platforms in the same invocation.
|
||||
- name: Build Linux and Mac releases
|
||||
run: ./gradlew runtimeZipOthers --console=plain
|
||||
|
||||
- name: Build Windows release
|
||||
run: ./gradlew runtimeZipWindows --console=plain
|
||||
|
||||
- name: Check release zips
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
missing=0
|
||||
for platform in linux-aarch64 linux-x86_64 osx-aarch64 osx-x86_64 windows-aarch64 windows-x86_64; do
|
||||
zip="build/image/sdr-trunk-${platform}-v${VERSION}.zip"
|
||||
if [ ! -f "$zip" ]; then
|
||||
echo "::error::Missing $zip"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
ls -l build/image/*.zip
|
||||
exit $missing
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: release-zips
|
||||
path: build/image/*.zip
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
publish:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: release-zips
|
||||
path: zips
|
||||
|
||||
# Publishing is split into steps that can all be repeated, so a failed
|
||||
# upload or a re-run finishes the draft left behind rather than failing on
|
||||
# it: gh retries uploads, and a retry of an upload that had in fact
|
||||
# succeeded fails with "ReleaseAsset.name already exists" unless clobbering.
|
||||
- name: Create release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
VERSION: ${{ needs.build.outputs.version }}
|
||||
PREVIOUS_TAG: ${{ needs.build.outputs.previous_tag }}
|
||||
run: |
|
||||
(cd zips && sha256sum *.zip > SHA256SUMS)
|
||||
prerelease=()
|
||||
if [[ "$GITHUB_REF_NAME" == *-* ]]; then
|
||||
prerelease=(--prerelease)
|
||||
fi
|
||||
notes_start=()
|
||||
if [ -n "$PREVIOUS_TAG" ]; then
|
||||
notes_start=(--notes-start-tag "$PREVIOUS_TAG")
|
||||
fi
|
||||
if gh release view "$GITHUB_REF_NAME" --json isDraft --jq .isDraft 2>/dev/null | grep -qx false; then
|
||||
echo "::error::Release $GITHUB_REF_NAME is already published"
|
||||
exit 1
|
||||
elif gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
|
||||
echo "Reusing the draft release $GITHUB_REF_NAME"
|
||||
else
|
||||
gh release create "$GITHUB_REF_NAME" --draft "${prerelease[@]}" \
|
||||
--verify-tag --generate-notes "${notes_start[@]}" --title "Version $VERSION"
|
||||
fi
|
||||
gh release upload "$GITHUB_REF_NAME" zips/* --clobber
|
||||
gh release edit "$GITHUB_REF_NAME" --draft=false
|
||||
25
README.md
25
README.md
@@ -49,3 +49,28 @@ committed to the code base, so it's not really 'nightly' as much as it is 'curre
|
||||
* **Operating System:** Windows (~~32 or~~ 64-bit), Linux (~~32 or~~ 64-bit) or Mac (64-bit, 12.x or higher)
|
||||
* **CPU:** 4-core
|
||||
* **RAM:** 8GB or more (preferred). Depending on usage, 4GB may be sufficient.
|
||||
|
||||
## Creating a Release (Maintainers)
|
||||
Releases are built and published automatically by the [Release](.github/workflows/release.yml) GitHub workflow
|
||||
whenever a version tag is pushed to the repository.
|
||||
|
||||
1. Update `projectVersion` in `gradle.properties` to the new version, following the naming convention described in
|
||||
that file (e.g. `0.6.2-alpha-1`, `0.6.2-beta-1` or `0.6.2`).
|
||||
2. Commit and push the change to `master`.
|
||||
3. Create and push a tag that matches the version with a leading `v`:
|
||||
```
|
||||
git tag v0.6.2-beta-1
|
||||
git push origin v0.6.2-beta-1
|
||||
```
|
||||
|
||||
The workflow then:
|
||||
* Verifies that the tag matches `projectVersion` in `gradle.properties` and fails if it does not.
|
||||
* Builds and tests the code, then creates the release zips for Linux, Mac and Windows (x86-64 and aarch64).
|
||||
* Creates a GitHub release titled `Version <version>` with auto-generated release notes (a "What's Changed" list of
|
||||
the PRs merged since the previous version tag), and attaches the six zip
|
||||
files plus a `SHA256SUMS` checksum file.
|
||||
* Marks tags containing a dash (alpha and beta versions) as pre-releases. Tags without a dash become full releases.
|
||||
|
||||
If the workflow fails part way through, fix the problem and re-run it from the Actions tab. It resumes from the
|
||||
draft release left behind. It won't modify a release that has already been published. To rebuild a published
|
||||
release, delete the release and the tag on GitHub, then push the tag again.
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
#ifndef SDRPLAY_API_H
|
||||
#define SDRPLAY_API_H
|
||||
|
||||
#include "sdrplay_api_dev.h"
|
||||
#include "sdrplay_api_rx_channel.h"
|
||||
#include "sdrplay_api_callback.h"
|
||||
|
||||
//#if defined(_M_X64) || defined(_M_IX86)
|
||||
//#include "windows.h"
|
||||
//#elif defined (__GNUC__)
|
||||
typedef void *HANDLE;
|
||||
//#endif
|
||||
|
||||
//#ifndef _SDRPLAY_DLL_QUALIFIER
|
||||
//#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86))
|
||||
//#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
|
||||
//#elif defined(STATIC_LIB) || defined(__GNUC__)
|
||||
//#define _SDRPLAY_DLL_QUALIFIER
|
||||
//#endif
|
||||
//#endif // _SDRPLAY_DLL_QUALIFIER
|
||||
|
||||
// Application code should check that it is compiled against the same API version
|
||||
// sdrplay_api_ApiVersion() returns the API version
|
||||
#define SDRPLAY_API_VERSION (float)(3.07)
|
||||
|
||||
// API Constants
|
||||
#define SDRPLAY_MAX_DEVICES (16)
|
||||
#define SDRPLAY_MAX_TUNERS_PER_DEVICE (2)
|
||||
|
||||
#define SDRPLAY_MAX_SER_NO_LEN (64)
|
||||
#define SDRPLAY_MAX_ROOT_NM_LEN (32)
|
||||
|
||||
#define SDRPLAY_RSP1_ID (1)
|
||||
#define SDRPLAY_RSP1A_ID (255)
|
||||
#define SDRPLAY_RSP2_ID (2)
|
||||
#define SDRPLAY_RSPduo_ID (3)
|
||||
#define SDRPLAY_RSPdx_ID (4)
|
||||
|
||||
// Enum types
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Success = 0,
|
||||
sdrplay_api_Fail = 1,
|
||||
sdrplay_api_InvalidParam = 2,
|
||||
sdrplay_api_OutOfRange = 3,
|
||||
sdrplay_api_GainUpdateError = 4,
|
||||
sdrplay_api_RfUpdateError = 5,
|
||||
sdrplay_api_FsUpdateError = 6,
|
||||
sdrplay_api_HwError = 7,
|
||||
sdrplay_api_AliasingError = 8,
|
||||
sdrplay_api_AlreadyInitialised = 9,
|
||||
sdrplay_api_NotInitialised = 10,
|
||||
sdrplay_api_NotEnabled = 11,
|
||||
sdrplay_api_HwVerError = 12,
|
||||
sdrplay_api_OutOfMemError = 13,
|
||||
sdrplay_api_ServiceNotResponding = 14,
|
||||
sdrplay_api_StartPending = 15,
|
||||
sdrplay_api_StopPending = 16,
|
||||
sdrplay_api_InvalidMode = 17,
|
||||
sdrplay_api_FailedVerification1 = 18,
|
||||
sdrplay_api_FailedVerification2 = 19,
|
||||
sdrplay_api_FailedVerification3 = 20,
|
||||
sdrplay_api_FailedVerification4 = 21,
|
||||
sdrplay_api_FailedVerification5 = 22,
|
||||
sdrplay_api_FailedVerification6 = 23,
|
||||
sdrplay_api_InvalidServiceVersion = 24
|
||||
} sdrplay_api_ErrT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Update_None = 0x00000000,
|
||||
|
||||
// Reasons for master only mode
|
||||
sdrplay_api_Update_Dev_Fs = 0x00000001,
|
||||
sdrplay_api_Update_Dev_Ppm = 0x00000002,
|
||||
sdrplay_api_Update_Dev_SyncUpdate = 0x00000004,
|
||||
sdrplay_api_Update_Dev_ResetFlags = 0x00000008,
|
||||
|
||||
sdrplay_api_Update_Rsp1a_BiasTControl = 0x00000010,
|
||||
sdrplay_api_Update_Rsp1a_RfNotchControl = 0x00000020,
|
||||
sdrplay_api_Update_Rsp1a_RfDabNotchControl = 0x00000040,
|
||||
|
||||
sdrplay_api_Update_Rsp2_BiasTControl = 0x00000080,
|
||||
sdrplay_api_Update_Rsp2_AmPortSelect = 0x00000100,
|
||||
sdrplay_api_Update_Rsp2_AntennaControl = 0x00000200,
|
||||
sdrplay_api_Update_Rsp2_RfNotchControl = 0x00000400,
|
||||
sdrplay_api_Update_Rsp2_ExtRefControl = 0x00000800,
|
||||
|
||||
sdrplay_api_Update_RspDuo_ExtRefControl = 0x00001000,
|
||||
|
||||
sdrplay_api_Update_Master_Spare_1 = 0x00002000,
|
||||
sdrplay_api_Update_Master_Spare_2 = 0x00004000,
|
||||
|
||||
// Reasons for master and slave mode
|
||||
// Note: sdrplay_api_Update_Tuner_Gr MUST be the first value defined in this section!
|
||||
sdrplay_api_Update_Tuner_Gr = 0x00008000,
|
||||
sdrplay_api_Update_Tuner_GrLimits = 0x00010000,
|
||||
sdrplay_api_Update_Tuner_Frf = 0x00020000,
|
||||
sdrplay_api_Update_Tuner_BwType = 0x00040000,
|
||||
sdrplay_api_Update_Tuner_IfType = 0x00080000,
|
||||
sdrplay_api_Update_Tuner_DcOffset = 0x00100000,
|
||||
sdrplay_api_Update_Tuner_LoMode = 0x00200000,
|
||||
|
||||
sdrplay_api_Update_Ctrl_DCoffsetIQimbalance = 0x00400000,
|
||||
sdrplay_api_Update_Ctrl_Decimation = 0x00800000,
|
||||
sdrplay_api_Update_Ctrl_Agc = 0x01000000,
|
||||
sdrplay_api_Update_Ctrl_AdsbMode = 0x02000000,
|
||||
sdrplay_api_Update_Ctrl_OverloadMsgAck = 0x04000000,
|
||||
|
||||
sdrplay_api_Update_RspDuo_BiasTControl = 0x08000000,
|
||||
sdrplay_api_Update_RspDuo_AmPortSelect = 0x10000000,
|
||||
sdrplay_api_Update_RspDuo_Tuner1AmNotchControl = 0x20000000,
|
||||
sdrplay_api_Update_RspDuo_RfNotchControl = 0x40000000,
|
||||
sdrplay_api_Update_RspDuo_RfDabNotchControl = 0x80000000,
|
||||
} sdrplay_api_ReasonForUpdateT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Update_Ext1_None = 0x00000000,
|
||||
|
||||
// Reasons for master only mode
|
||||
sdrplay_api_Update_RspDx_HdrEnable = 0x00000001,
|
||||
sdrplay_api_Update_RspDx_BiasTControl = 0x00000002,
|
||||
sdrplay_api_Update_RspDx_AntennaControl = 0x00000004,
|
||||
sdrplay_api_Update_RspDx_RfNotchControl = 0x00000008,
|
||||
sdrplay_api_Update_RspDx_RfDabNotchControl = 0x00000010,
|
||||
sdrplay_api_Update_RspDx_HdrBw = 0x00000020,
|
||||
|
||||
// Reasons for master and slave mode
|
||||
} sdrplay_api_ReasonForUpdateExtension1T;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_DbgLvl_Disable = 0,
|
||||
sdrplay_api_DbgLvl_Verbose = 1,
|
||||
sdrplay_api_DbgLvl_Warning = 2,
|
||||
sdrplay_api_DbgLvl_Error = 3,
|
||||
sdrplay_api_DbgLvl_Message = 4,
|
||||
} sdrplay_api_DbgLvl_t;
|
||||
|
||||
// Device structure
|
||||
typedef struct
|
||||
{
|
||||
char SerNo[SDRPLAY_MAX_SER_NO_LEN];
|
||||
unsigned char hwVer;
|
||||
sdrplay_api_TunerSelectT tuner;
|
||||
sdrplay_api_RspDuoModeT rspDuoMode;
|
||||
double rspDuoSampleFreq;
|
||||
HANDLE dev;
|
||||
} sdrplay_api_DeviceT;
|
||||
|
||||
// Device parameter structure
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_DevParamsT *devParams;
|
||||
sdrplay_api_RxChannelParamsT *rxChannelA;
|
||||
sdrplay_api_RxChannelParamsT *rxChannelB;
|
||||
} sdrplay_api_DeviceParamsT;
|
||||
|
||||
// Extended error message structure
|
||||
typedef struct
|
||||
{
|
||||
char file[256];
|
||||
char function[256];
|
||||
int line;
|
||||
char message[1024];
|
||||
} sdrplay_api_ErrorInfoT;
|
||||
|
||||
// Comman API function types
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_Open_t)(void);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_Close_t)(void);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_ApiVersion_t)(float *apiVer);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_LockDeviceApi_t)(void);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_UnlockDeviceApi_t)(void);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_GetDevices_t)(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_SelectDevice_t)(sdrplay_api_DeviceT *device);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_ReleaseDevice_t)(sdrplay_api_DeviceT *device);
|
||||
typedef const char* (*sdrplay_api_GetErrorString_t)(sdrplay_api_ErrT err);
|
||||
typedef sdrplay_api_ErrorInfoT* (*sdrplay_api_GetLastError_t)(sdrplay_api_DeviceT *device);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_DisableHeartbeat_t)(void);
|
||||
|
||||
// Device API function types
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_DebugEnable_t)(HANDLE dev, sdrplay_api_DbgLvl_t dbgLvl);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_GetDeviceParams_t)(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_Init_t)(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_Uninit_t)(HANDLE dev);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_Update_t)(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoActiveTuner_t)(HANDLE dev, sdrplay_api_TunerSelectT *tuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoDualTunerModeSampleRate_t)(double *currentSampleRate);
|
||||
typedef sdrplay_api_ErrT (*sdrplay_api_SwapRspDuoMode_t)(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
|
||||
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
|
||||
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||
|
||||
// API function definitions
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// Comman API function definitions
|
||||
sdrplay_api_ErrT sdrplay_api_Open(void);
|
||||
sdrplay_api_ErrT sdrplay_api_Close(void);
|
||||
sdrplay_api_ErrT sdrplay_api_ApiVersion(float *apiVer);
|
||||
sdrplay_api_ErrT sdrplay_api_LockDeviceApi(void);
|
||||
sdrplay_api_ErrT sdrplay_api_UnlockDeviceApi(void);
|
||||
sdrplay_api_ErrT sdrplay_api_GetDevices(sdrplay_api_DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs);
|
||||
sdrplay_api_ErrT sdrplay_api_SelectDevice(sdrplay_api_DeviceT *device);
|
||||
sdrplay_api_ErrT sdrplay_api_ReleaseDevice(sdrplay_api_DeviceT *device);
|
||||
const char* sdrplay_api_GetErrorString(sdrplay_api_ErrT err);
|
||||
sdrplay_api_ErrorInfoT* sdrplay_api_GetLastError(sdrplay_api_DeviceT *device);
|
||||
sdrplay_api_ErrT sdrplay_api_DisableHeartbeat(void); // Must be called before sdrplay_api_SelectDevice()
|
||||
|
||||
// Device API function definitions
|
||||
sdrplay_api_ErrT sdrplay_api_DebugEnable(HANDLE dev, sdrplay_api_DbgLvl_t enable);
|
||||
sdrplay_api_ErrT sdrplay_api_GetDeviceParams(HANDLE dev, sdrplay_api_DeviceParamsT **deviceParams);
|
||||
sdrplay_api_ErrT sdrplay_api_Init(HANDLE dev, sdrplay_api_CallbackFnsT *callbackFns, void *cbContext);
|
||||
sdrplay_api_ErrT sdrplay_api_Uninit(HANDLE dev);
|
||||
sdrplay_api_ErrT sdrplay_api_Update(HANDLE dev, sdrplay_api_TunerSelectT tuner, sdrplay_api_ReasonForUpdateT reasonForUpdate, sdrplay_api_ReasonForUpdateExtension1T reasonForUpdateExt1);
|
||||
sdrplay_api_ErrT sdrplay_api_SwapRspDuoActiveTuner(HANDLE dev, sdrplay_api_TunerSelectT *currentTuner, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||
sdrplay_api_ErrT sdrplay_api_SwapRspDuoDualTunerModeSampleRate(HANDLE dev, double *currentSampleRate);
|
||||
sdrplay_api_ErrT sdrplay_api_SwapRspDuoMode(sdrplay_api_DeviceT *currDevice, sdrplay_api_DeviceParamsT **deviceParams,
|
||||
sdrplay_api_RspDuoModeT rspDuoMode, double sampleRate, sdrplay_api_TunerSelectT tuner,
|
||||
sdrplay_api_Bw_MHzT bwType, sdrplay_api_If_kHzT ifType, sdrplay_api_RspDuo_AmPortSelectT tuner1AmPortSel);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //SDRPLAY_API_H
|
||||
@@ -1,78 +0,0 @@
|
||||
#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
|
||||
@@ -1,55 +0,0 @@
|
||||
#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
|
||||
@@ -1,51 +0,0 @@
|
||||
#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
|
||||
@@ -1,22 +0,0 @@
|
||||
#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
|
||||
@@ -1,35 +0,0 @@
|
||||
#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
|
||||
@@ -1,40 +0,0 @@
|
||||
#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
|
||||
@@ -1,45 +0,0 @@
|
||||
#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
|
||||
@@ -1,21 +0,0 @@
|
||||
#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
|
||||
@@ -1,93 +0,0 @@
|
||||
#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
|
||||
@@ -1,230 +0,0 @@
|
||||
#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
|
||||
@@ -1,78 +0,0 @@
|
||||
#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
|
||||
@@ -1,55 +0,0 @@
|
||||
#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
|
||||
@@ -1,51 +0,0 @@
|
||||
#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
|
||||
@@ -1,22 +0,0 @@
|
||||
#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
|
||||
@@ -1,35 +0,0 @@
|
||||
#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
|
||||
@@ -1,40 +0,0 @@
|
||||
#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
|
||||
@@ -1,45 +0,0 @@
|
||||
#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
|
||||
@@ -1,21 +0,0 @@
|
||||
#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
|
||||
@@ -1,93 +0,0 @@
|
||||
#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
|
||||
@@ -1,235 +0,0 @@
|
||||
#ifndef SDRPLAY_API_H
|
||||
#define SDRPLAY_API_H
|
||||
|
||||
#include "sdrplay_api_dev.h"
|
||||
#include "sdrplay_api_rx_channel.h"
|
||||
#include "sdrplay_api_callback.h"
|
||||
|
||||
#if defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64)
|
||||
#include "windows.h"
|
||||
#elif defined (__GNUC__)
|
||||
typedef void *HANDLE;
|
||||
#endif
|
||||
|
||||
#ifndef _SDRPLAY_DLL_QUALIFIER
|
||||
#if !defined(STATIC_LIB) && (defined(_M_X64) || defined(_M_IX86) || defined(_M_ARM64))
|
||||
#define _SDRPLAY_DLL_QUALIFIER __declspec(dllimport)
|
||||
#elif defined(STATIC_LIB) || defined(__GNUC__)
|
||||
#define _SDRPLAY_DLL_QUALIFIER
|
||||
#endif
|
||||
#endif // _SDRPLAY_DLL_QUALIFIER
|
||||
|
||||
// Application code should check that it is compiled against the same API version
|
||||
// sdrplay_api_ApiVersion() returns the API version
|
||||
#define SDRPLAY_API_VERSION (float)(3.15)
|
||||
|
||||
// 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)
|
||||
#define SDRPLAY_RSP1B_ID (6)
|
||||
#define SDRPLAY_RSPdxR2_ID (7)
|
||||
|
||||
// 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,
|
||||
sdrplay_api_Update_RspDuo_ResetSlaveFlags = 0x00000040,
|
||||
|
||||
// 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_ErrorInfoT* (*sdrplay_api_GetLastErrorByType_t)(sdrplay_api_DeviceT *device, int type, unsigned long long *time);
|
||||
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_ErrorInfoT* sdrplay_api_GetLastErrorByType(sdrplay_api_DeviceT *device, int type, unsigned long long *time);
|
||||
_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
|
||||
@@ -1,79 +0,0 @@
|
||||
#ifndef SDRPLAY_API_CALLBACK_H
|
||||
#define SDRPLAY_API_CALLBACK_H
|
||||
|
||||
// Event callback enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_Overload_Detected = 0,
|
||||
sdrplay_api_Overload_Corrected = 1,
|
||||
} sdrplay_api_PowerOverloadCbEventIdT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_MasterInitialised = 0,
|
||||
sdrplay_api_SlaveAttached = 1,
|
||||
sdrplay_api_SlaveDetached = 2,
|
||||
sdrplay_api_SlaveInitialised = 3,
|
||||
sdrplay_api_SlaveUninitialised = 4,
|
||||
sdrplay_api_MasterDllDisappeared = 5,
|
||||
sdrplay_api_SlaveDllDisappeared = 6,
|
||||
} sdrplay_api_RspDuoModeCbEventIdT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_GainChange = 0,
|
||||
sdrplay_api_PowerOverloadChange = 1,
|
||||
sdrplay_api_DeviceRemoved = 2,
|
||||
sdrplay_api_RspDuoModeChange = 3,
|
||||
sdrplay_api_DeviceFailure = 4,
|
||||
} sdrplay_api_EventT;
|
||||
|
||||
// Event callback parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned int gRdB;
|
||||
unsigned int lnaGRdB;
|
||||
double currGain;
|
||||
} sdrplay_api_GainCbParamT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_PowerOverloadCbEventIdT powerOverloadChangeType;
|
||||
} sdrplay_api_PowerOverloadCbParamT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_RspDuoModeCbEventIdT modeChangeType;
|
||||
} sdrplay_api_RspDuoModeCbParamT;
|
||||
|
||||
// Event parameters overlay
|
||||
typedef union
|
||||
{
|
||||
sdrplay_api_GainCbParamT gainParams;
|
||||
sdrplay_api_PowerOverloadCbParamT powerOverloadParams;
|
||||
sdrplay_api_RspDuoModeCbParamT rspDuoModeParams;
|
||||
} sdrplay_api_EventParamsT;
|
||||
|
||||
// Stream callback parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned int firstSampleNum;
|
||||
int grChanged;
|
||||
int rfChanged;
|
||||
int fsChanged;
|
||||
unsigned int numSamples;
|
||||
} sdrplay_api_StreamCbParamsT;
|
||||
|
||||
// Callback function prototypes
|
||||
typedef void (*sdrplay_api_StreamCallback_t)(short *xi, short *xq, sdrplay_api_StreamCbParamsT *params, unsigned int numSamples, unsigned int reset, void *cbContext);
|
||||
typedef void (*sdrplay_api_EventCallback_t)(sdrplay_api_EventT eventId, sdrplay_api_TunerSelectT tuner, sdrplay_api_EventParamsT *params, void *cbContext);
|
||||
|
||||
// Callback function struct
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_StreamCallback_t StreamACbFn;
|
||||
sdrplay_api_StreamCallback_t StreamBCbFn;
|
||||
sdrplay_api_EventCallback_t EventCbFn;
|
||||
} sdrplay_api_CallbackFnsT;
|
||||
|
||||
#endif //SDRPLAY_API_CALLBACK_H
|
||||
@@ -1,55 +0,0 @@
|
||||
#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
|
||||
@@ -1,51 +0,0 @@
|
||||
#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
|
||||
@@ -1,22 +0,0 @@
|
||||
#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
|
||||
@@ -1,35 +0,0 @@
|
||||
#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
|
||||
@@ -1,47 +0,0 @@
|
||||
#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 resetGainUpdate; // default: 0
|
||||
unsigned char resetRfUpdate; // default: 0
|
||||
} sdrplay_api_RspDuo_ResetSlaveFlagsT;
|
||||
|
||||
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_RspDuo_ResetSlaveFlagsT resetSlaveFlags;
|
||||
} sdrplay_api_RspDuoTunerParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_RSPduo_H
|
||||
@@ -1,46 +0,0 @@
|
||||
#ifndef SDRPLAY_API_RSPDX_H
|
||||
#define SDRPLAY_API_RSPDX_H
|
||||
|
||||
#include "sdrplay_api_tuner.h"
|
||||
|
||||
#define RSPDX_NUM_LNA_STATES 28 // Number of LNA states in all bands (except where defined differently below)
|
||||
#define RSPDX_NUM_LNA_STATES_AMPORT2_0_12 19 // Number of LNA states when using AM Port 2 between 0 and 12MHz
|
||||
#define RSPDX_NUM_LNA_STATES_AMPORT2_12_50 20 // Number of LNA states when using AM Port 2 between 12 and 50MHz
|
||||
#define RSPDX_NUM_LNA_STATES_AMPORT2_50_60 25 // Number of LNA states when using AM Port 2 between 50 and 60MHz
|
||||
#define RSPDX_NUM_LNA_STATES_VHF_BAND3 27 // Number of LNA states in VHF and Band3
|
||||
#define RSPDX_NUM_LNA_STATES_420MHZ 21 // Number of LNA states in 420MHz band
|
||||
#define RSPDX_NUM_LNA_STATES_LBAND 19 // Number of LNA states in L-band
|
||||
#define RSPDX_NUM_LNA_STATES_DX 22 // Number of LNA states in DX path
|
||||
|
||||
// RSPdx parameter enums
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_RspDx_ANTENNA_A = 0,
|
||||
sdrplay_api_RspDx_ANTENNA_B = 1,
|
||||
sdrplay_api_RspDx_ANTENNA_C = 2,
|
||||
} sdrplay_api_RspDx_AntennaSelectT;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
sdrplay_api_RspDx_HDRMODE_BW_0_200 = 0,
|
||||
sdrplay_api_RspDx_HDRMODE_BW_0_500 = 1,
|
||||
sdrplay_api_RspDx_HDRMODE_BW_1_200 = 2,
|
||||
sdrplay_api_RspDx_HDRMODE_BW_1_700 = 3,
|
||||
} sdrplay_api_RspDx_HdrModeBwT;
|
||||
|
||||
// RSPdx parameter structs
|
||||
typedef struct
|
||||
{
|
||||
unsigned char hdrEnable; // default: 0
|
||||
unsigned char biasTEnable; // default: 0
|
||||
sdrplay_api_RspDx_AntennaSelectT antennaSel; // default: sdrplay_api_RspDx_ANTENNA_A
|
||||
unsigned char rfNotchEnable; // default: 0
|
||||
unsigned char rfDabNotchEnable; // default: 0
|
||||
} sdrplay_api_RspDxParamsT;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
sdrplay_api_RspDx_HdrModeBwT hdrBw; // default: sdrplay_api_RspDx_HDRMODE_BW_1_700
|
||||
} sdrplay_api_RspDxTunerParamsT;
|
||||
|
||||
#endif //SDRPLAY_API_RSPDX_H
|
||||
@@ -1,21 +0,0 @@
|
||||
#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
|
||||
@@ -1,93 +0,0 @@
|
||||
#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
|
||||
48
build.gradle
48
build.gradle
@@ -63,6 +63,31 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the selected toolchain JDK includes the JavaFX modules (e.g. Bellsoft Liberica 'Full JDK').
|
||||
*
|
||||
* Gradle's toolchain spec can only select by language version, vendor and implementation, so it can't distinguish
|
||||
* between the Liberica JDK packages that do and don't bundle JavaFX. This task fails the build with a clear message
|
||||
* instead of allowing compilation to fail with missing javafx packages.
|
||||
*/
|
||||
tasks.register('verifyToolchainJavaFx') {
|
||||
def jdkHome = javaToolchains.launcherFor(java.toolchain).map { it.metadata.installationPath.asFile }
|
||||
|
||||
doLast {
|
||||
def release = new File(jdkHome.get(), 'release')
|
||||
|
||||
if(!release.exists() || !release.text.contains('javafx.base')) {
|
||||
throw new GradleException("The JDK at [${jdkHome.get()}] does not include the JavaFX modules. Install " +
|
||||
"the Bellsoft Liberica JDK 27 'Full' package (includes LibericaFX), or use " +
|
||||
"-Porg.gradle.java.installations.paths=<jdk path> to specify the JDK that Gradle should use.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('compileJava') {
|
||||
dependsOn 'verifyToolchainJavaFx'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -84,7 +109,7 @@ dependencies {
|
||||
//mavenCentral/jcenter imports
|
||||
implementation 'ch.qos.logback:logback-classic:1.6.3'
|
||||
implementation 'ch.qos.logback:logback-core:1.5.38'
|
||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.22.1'
|
||||
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.22.4'
|
||||
implementation 'com.fazecast:jSerialComm:2.11.4'
|
||||
implementation 'com.formdev:flatlaf:3.7.2'
|
||||
implementation 'com.formdev:flatlaf-intellij-themes:3.7.2'
|
||||
@@ -99,12 +124,16 @@ dependencies {
|
||||
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||
implementation 'commons-io:commons-io:2.22.0'
|
||||
implementation 'eu.hansolo:charts:1.0.5'
|
||||
implementation 'io.github.dsheirer:jbladerf:2.6.0' //bladeRF tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jhydrasdr:1.1.3' //HydraSDR tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jairspy:1.0.12.1' //Airspy tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jairspyhf:1.8.1.1' //Airspy HF+ tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jbladerf:2.6.0.1' //bladeRF tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jhackrf:2026.01.3.1' //HackRF tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jlibusb:1.0.30.1' //libusb and USB hotplug for the tuner libraries - GitHub release; not pulled in transitively
|
||||
implementation 'io.github.dsheirer:jhydrasdr:1.1.4.1' //HydraSDR tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jlimesdr:25.1.0.2' //LimeSDR tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jrtl:1.4.0.2' //RTL-SDR tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:jsdrplay:3.15.3' //SDRplay RSP tuners - GitHub release, see settings.gradle
|
||||
implementation 'io.github.dsheirer:radio-reference-api:18.0.0'
|
||||
implementation 'io.github.dsheirer:usb4java-native-libraries:1.3.3' //OSX & Windows aarch64 native libs
|
||||
implementation 'io.github.dsheirer:usb4java:1.3.5' //Fork with Loader support for OSX Multi-Version
|
||||
implementation 'javax.usb:usb-api:1.0.2'
|
||||
implementation 'net.coderazzi:tablefilter-swing:5.5.4'
|
||||
implementation 'org.apache.commons:commons-compress:1.28.0'
|
||||
implementation 'org.apache.commons:commons-lang3:3.20.0'
|
||||
@@ -115,8 +144,6 @@ dependencies {
|
||||
implementation 'org.controlsfx:controlsfx:11.2.3'
|
||||
implementation 'org.rauschig:jarchivelib:1.2.0'
|
||||
implementation 'org.slf4j:slf4j-api:2.0.17'
|
||||
implementation 'org.usb4java:libusb4java:1.3.0'
|
||||
implementation 'org.usb4java:usb4java-javax:1.3.0'
|
||||
implementation 'pl.edu.icm:JLargeArrays:1.6'
|
||||
}
|
||||
|
||||
@@ -140,8 +167,7 @@ def jvmArgsWindows =
|
||||
'--enable-preview', //Project Panama Vector API is still in incubator
|
||||
'--enable-native-access=ALL-UNNAMED', //Suppress unsafe warnings for FFM API Foreign Memory calls
|
||||
'--enable-native-access=javafx.graphics', //JDK24 - suppress restricted java.lang.System calls
|
||||
'--sun-misc-unsafe-memory-access=allow', //JDK24 - suppress sun.misc.Unsafe::allocateMemory warning
|
||||
'-Djava.library.path=c:\\Program Files\\SDRplay\\API\\x64'] //SDRPlay API library path
|
||||
'--sun-misc-unsafe-memory-access=allow'] //JDK24 - suppress sun.misc.Unsafe::allocateMemory warning
|
||||
|
||||
/**
|
||||
* JVM Arguments for running the application on Linux.
|
||||
@@ -152,7 +178,7 @@ def jvmArgsLinux =
|
||||
'--enable-preview', //Project Panama Vector & Foreign Function APIs remain in incubator
|
||||
'--sun-misc-unsafe-memory-access=allow', //JDK24 - suppress sun.misc.Unsafe::allocateMemory warning
|
||||
'--enable-native-access=javafx.graphics', //JDK24 - suppress restricted java.lang.System calls
|
||||
'--enable-native-access=ALL-UNNAMED'] //SDRPlay API library path
|
||||
'--enable-native-access=ALL-UNNAMED'] //Suppress warnings for FFM API native calls (tuner libraries)
|
||||
|
||||
application {
|
||||
mainClass = "io.github.dsheirer.gui.SDRTrunk"
|
||||
|
||||
@@ -28,4 +28,4 @@
|
||||
# tag name, specifically the dashes inserted before and after the alpha/beta labels.
|
||||
# See: https://github.com/DSheirer/sdrtrunk/issues/1651
|
||||
|
||||
projectVersion=0.6.2-beta-1
|
||||
projectVersion=1.0.0-beta-1
|
||||
@@ -9,6 +9,47 @@ dependencyResolutionManagement {
|
||||
mavenCentral()
|
||||
maven { url 'https://jitpack.io' }
|
||||
|
||||
//jAirspy is published as a GitHub release, with the native libraries for all platforms in the jar
|
||||
ivy {
|
||||
url = 'https://github.com/DSheirer/jAirspy/releases/download'
|
||||
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
|
||||
metadataSources { artifact() }
|
||||
content { includeModule 'io.github.dsheirer', 'jairspy' }
|
||||
}
|
||||
|
||||
//jAirspyHF is published as a GitHub release, with the native libraries for all platforms in the jar
|
||||
ivy {
|
||||
url = 'https://github.com/DSheirer/jAirspyHF/releases/download'
|
||||
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
|
||||
metadataSources { artifact() }
|
||||
content { includeModule 'io.github.dsheirer', 'jairspyhf' }
|
||||
}
|
||||
|
||||
//jRTL is published as a GitHub release, with the native libraries for all platforms in the jar
|
||||
ivy {
|
||||
url = 'https://github.com/DSheirer/jRTL/releases/download'
|
||||
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
|
||||
metadataSources { artifact() }
|
||||
content { includeModule 'io.github.dsheirer', 'jrtl' }
|
||||
}
|
||||
|
||||
//jlibusb is published as a GitHub release, with libusb for all platforms in the jar. Tuner libraries built on
|
||||
//jlibusb (jAirspy, jAirspyHF, jBladeRF, jHackRF, jHydraSDR, jLimeSDR, jRTL) load their native library next to its libusb, so the process shares one copy.
|
||||
ivy {
|
||||
url = 'https://github.com/DSheirer/jlibusb/releases/download'
|
||||
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
|
||||
metadataSources { artifact() }
|
||||
content { includeModule 'io.github.dsheirer', 'jlibusb' }
|
||||
}
|
||||
|
||||
//jHackRF is published as a GitHub release, with the native libraries for all platforms in the jar
|
||||
ivy {
|
||||
url = 'https://github.com/DSheirer/jHackRF/releases/download'
|
||||
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
|
||||
metadataSources { artifact() }
|
||||
content { includeModule 'io.github.dsheirer', 'jhackrf' }
|
||||
}
|
||||
|
||||
//jHydraSDR is published as a GitHub release, with the native libraries for all platforms in the jar
|
||||
ivy {
|
||||
url = 'https://github.com/DSheirer/jHydraSDR/releases/download'
|
||||
@@ -17,6 +58,23 @@ dependencyResolutionManagement {
|
||||
content { includeModule 'io.github.dsheirer', 'jhydrasdr' }
|
||||
}
|
||||
|
||||
//jLimeSDR is published as a GitHub release, with the native libraries for all platforms in the jar
|
||||
ivy {
|
||||
url = 'https://github.com/DSheirer/jLimeSDR/releases/download'
|
||||
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
|
||||
metadataSources { artifact() }
|
||||
content { includeModule 'io.github.dsheirer', 'jlimesdr' }
|
||||
}
|
||||
|
||||
//jsdrplay is published as a GitHub release. The jar does not contain the SDRplay API native library, which
|
||||
//users install separately from sdrplay.com
|
||||
ivy {
|
||||
url = 'https://github.com/DSheirer/jsdrplay/releases/download'
|
||||
patternLayout { artifact 'v[revision]/[artifact]-[revision].[ext]' }
|
||||
metadataSources { artifact() }
|
||||
content { includeModule 'io.github.dsheirer', 'jsdrplay' }
|
||||
}
|
||||
|
||||
//jBladeRF is published as a GitHub release, with the native libraries for all platforms in the jar
|
||||
ivy {
|
||||
url = 'https://github.com/DSheirer/jBladeRF/releases/download'
|
||||
|
||||
@@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory;
|
||||
public abstract class AbstractAudioModule extends Module implements IAudioSegmentProvider, IdentifierUpdateListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AbstractAudioModule.class);
|
||||
public static final long DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH = 60 * 8000; // 1 minute @ 8kHz
|
||||
public static final long DEFAULT_MAX_SEGMENT_LENGTH_MILLIS = 60_000; // 1 minute
|
||||
public static final int DEFAULT_TIMESLOT = 0;
|
||||
private final int mMaxSegmentAudioSampleLength;
|
||||
private Listener<AudioSegment> mAudioSegmentListener;
|
||||
@@ -51,12 +51,12 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
||||
* Constructs an abstract audio module
|
||||
*
|
||||
* @param aliasList for aliasing identifiers
|
||||
* @param maxSegmentAudioSampleLength in milliseconds
|
||||
* @param maxSegmentLengthMillis maximum audio segment length in milliseconds
|
||||
*/
|
||||
public AbstractAudioModule(AliasList aliasList, int timeslot, long maxSegmentAudioSampleLength)
|
||||
public AbstractAudioModule(AliasList aliasList, int timeslot, long maxSegmentLengthMillis)
|
||||
{
|
||||
mAliasList = aliasList;
|
||||
mMaxSegmentAudioSampleLength = (int)(maxSegmentAudioSampleLength * 8); //Convert milliseconds to samples
|
||||
mMaxSegmentAudioSampleLength = (int)(maxSegmentLengthMillis * 8); //Convert milliseconds to samples
|
||||
mTimeslot = timeslot;
|
||||
mIdentifierCollection = new MutableIdentifierCollection(getTimeslot());
|
||||
mIdentifierUpdateNotificationBroadcaster.addListener(mIdentifierCollection);
|
||||
@@ -67,7 +67,7 @@ public abstract class AbstractAudioModule extends Module implements IAudioSegmen
|
||||
*/
|
||||
public AbstractAudioModule(AliasList aliasList)
|
||||
{
|
||||
this(aliasList, DEFAULT_TIMESLOT, DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH);
|
||||
this(aliasList, DEFAULT_TIMESLOT, DEFAULT_MAX_SEGMENT_LENGTH_MILLIS);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -59,7 +59,7 @@ public abstract class JmbeAudioModule extends AbstractAudioModule implements Lis
|
||||
|
||||
public JmbeAudioModule(UserPreferences userPreferences, AliasList aliasList, int timeslot)
|
||||
{
|
||||
super(aliasList, timeslot, DEFAULT_SEGMENT_AUDIO_SAMPLE_LENGTH);
|
||||
super(aliasList, timeslot, DEFAULT_MAX_SEGMENT_LENGTH_MILLIS);
|
||||
mUserPreferences = userPreferences;
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
loadConverter();
|
||||
|
||||
@@ -46,6 +46,12 @@ public class AudioChannel implements Listener<IdentifierUpdateNotification>
|
||||
* sample rate.
|
||||
*/
|
||||
public static final int SAMPLES_PER_INTERVAL = 160;
|
||||
/**
|
||||
* Quantity of consecutive intervals without new audio (1 second) before an incomplete audio segment is treated as
|
||||
* stalled and abandoned. An audio segment is delivered once to a single channel, so abandoning a segment that is
|
||||
* still live loses the rest of the call. This has to tolerate decoder gaps, such as a missed P25 LDU (180 ms).
|
||||
*/
|
||||
static final int MAX_STALLED_INTERVALS = 50;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AudioChannel.class);
|
||||
private final AudioBuffer mAudioBuffer = new AudioBuffer();
|
||||
private final Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
||||
@@ -225,7 +231,7 @@ public class AudioChannel implements Listener<IdentifierUpdateNotification>
|
||||
}
|
||||
|
||||
//Dispose completed/fully-played audio segments and segments that have stalled/failed to progress
|
||||
if(mCurrentAudioSegment != null && ((mNoAudioFromSegmentIntervalCount >= 6) ||
|
||||
if(mCurrentAudioSegment != null && ((mNoAudioFromSegmentIntervalCount >= MAX_STALLED_INTERVALS) ||
|
||||
(mCurrentAudioSegment.isComplete() && (mCurrentBufferIndex >= mCurrentAudioSegment.getAudioBufferCount()))))
|
||||
{
|
||||
disposeCurrentAudioSegment();
|
||||
@@ -401,6 +407,24 @@ public class AudioChannel implements Listener<IdentifierUpdateNotification>
|
||||
return mAudioSegmentQueue.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Next audio segment queued for playback, without removing it from the queue.
|
||||
* @return queued audio segment or null if the queue is empty.
|
||||
*/
|
||||
AudioSegment peekQueuedAudioSegment()
|
||||
{
|
||||
return mAudioSegmentQueue.peek();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the next audio segment from the queue, transferring it (and its consumer count) to the caller.
|
||||
* @return queued audio segment or null if the queue is empty.
|
||||
*/
|
||||
AudioSegment pollQueuedAudioSegment()
|
||||
{
|
||||
return mAudioSegmentQueue.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules the audio segment for playback. The audio segment user count should already be incremented by the
|
||||
* calling entity.
|
||||
@@ -475,6 +499,7 @@ public class AudioChannel implements Listener<IdentifierUpdateNotification>
|
||||
}
|
||||
|
||||
mCurrentBufferIndex = -1;
|
||||
mNoAudioFromSegmentIntervalCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -173,6 +173,7 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
{
|
||||
Iterator<AudioSegment> it = mAudioSegments.iterator();
|
||||
AudioSegment audioSegment;
|
||||
List<AudioSegment> displaced = new ArrayList<>();
|
||||
|
||||
//Remove any audio segments flagged as do not monitor. Don't remove completed segments yet, because
|
||||
//we want to give them a brief chance at playback. Automatically assign linked audio segments to the
|
||||
@@ -200,6 +201,16 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
if(audioOutput.isLinkedTo(audioSegment))
|
||||
{
|
||||
it.remove();
|
||||
|
||||
//Linked audio continues the current call, so it goes ahead of anything queued
|
||||
AudioSegment queued = audioOutput.pollQueuedAudioSegment();
|
||||
|
||||
while(queued != null)
|
||||
{
|
||||
displaced.add(queued);
|
||||
queued = audioOutput.pollQueuedAudioSegment();
|
||||
}
|
||||
|
||||
audioOutput.play(audioSegment);
|
||||
}
|
||||
}
|
||||
@@ -212,6 +223,8 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
}
|
||||
}
|
||||
|
||||
mAudioSegments.addAll(displaced);
|
||||
|
||||
//Sort audio segments by playback priority and assign to empty audio outputs
|
||||
if(!mAudioSegments.isEmpty() && mAudioOutput != null)
|
||||
{
|
||||
@@ -232,6 +245,8 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
replaceLowerPriorityQueuedSegments(mAudioSegments, mAudioOutput.getAudioProvider().getAudioChannels());
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -255,6 +270,40 @@ public class AudioPlaybackManager implements Listener<AudioSegment>, IAudioContr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps audio segments queued on the audio channels for waiting audio segments that have a higher playback priority,
|
||||
* so that a segment queued behind the current playback can't hold a higher priority segment out of the channel.
|
||||
* Audio that is currently playing is not interrupted. Displaced segments are returned to the waiting list.
|
||||
*
|
||||
* @param waiting audio segments, sorted by playback priority.
|
||||
* @param audioChannels to evaluate.
|
||||
*/
|
||||
static void replaceLowerPriorityQueuedSegments(List<AudioSegment> waiting, List<AudioChannel> audioChannels)
|
||||
{
|
||||
for(AudioChannel audioChannel: audioChannels)
|
||||
{
|
||||
if(waiting.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AudioSegment queued = audioChannel.peekQueuedAudioSegment();
|
||||
|
||||
if(queued != null &&
|
||||
waiting.getFirst().monitorPriorityProperty().get() < queued.monitorPriorityProperty().get())
|
||||
{
|
||||
//The audio thread may have taken the queued segment since the peek, so only return what we removed.
|
||||
queued = audioChannel.pollQueuedAudioSegment();
|
||||
audioChannel.play(waiting.removeFirst());
|
||||
|
||||
if(queued != null)
|
||||
{
|
||||
waiting.add(queued);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
|
||||
@@ -30,6 +30,16 @@ import javax.sound.sampled.AudioFormat;
|
||||
*/
|
||||
public abstract class AudioProvider
|
||||
{
|
||||
/**
|
||||
* Converts the audio sample to a 16-bit PCM sample, limiting out of range values instead of allowing them to wrap.
|
||||
* @param sample in range -1.0 to 1.0
|
||||
* @return PCM sample
|
||||
*/
|
||||
protected static short toPcm(float sample)
|
||||
{
|
||||
return (short)(Math.clamp(sample, -1.0f, 1.0f) * Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio format for this provider
|
||||
*/
|
||||
|
||||
@@ -77,7 +77,7 @@ public class AudioProviderMono extends AudioProvider
|
||||
|
||||
for(int x = 0; x < AudioChannel.SAMPLES_PER_INTERVAL; x++)
|
||||
{
|
||||
shortBuffer.put((short) (audio[x] * Short.MAX_VALUE));
|
||||
shortBuffer.put(toPcm(audio[x]));
|
||||
}
|
||||
|
||||
return buffer;
|
||||
|
||||
@@ -108,7 +108,7 @@ public class AudioProviderMultiChannel extends AudioProvider
|
||||
{
|
||||
for(int channelNumber = 0; channelNumber < mAudioChannels.size(); channelNumber++)
|
||||
{
|
||||
shortBuffer.put((short)(samples[channelNumber][sampleCounter] * Short.MAX_VALUE));
|
||||
shortBuffer.put(toPcm(samples[channelNumber][sampleCounter]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,8 +102,8 @@ public class AudioProviderStereo extends AudioProvider
|
||||
|
||||
for(int x = 0; x < AudioChannel.SAMPLES_PER_INTERVAL; x++)
|
||||
{
|
||||
shortBuffer.put((short)(left[x] * Short.MAX_VALUE));
|
||||
shortBuffer.put((short)(right[x] * Short.MAX_VALUE));
|
||||
shortBuffer.put(toPcm(left[x]));
|
||||
shortBuffer.put(toPcm(right[x]));
|
||||
}
|
||||
|
||||
return buffer;
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.hilbert.HilbertTransform;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Base complex samples iterator for raw sample buffers. Incorporates a Hilbert transform filter
|
||||
* for converting the real sample array to complex samples.
|
||||
* @param <T> either ComplexSamples or InterleavedComplexSamples
|
||||
*/
|
||||
public abstract class SampleBufferIterator<T> implements Iterator<T>
|
||||
{
|
||||
protected static final float SCALE_SIGNED_12_BIT_TO_FLOAT = 1.0f / 2048.0f;
|
||||
protected static final int FRAGMENT_SIZE = 4096;
|
||||
protected static final float[] COEFFICIENTS =
|
||||
HilbertTransform.convertHalfBandToHilbert(HilbertTransform.HALF_BAND_FILTER_47_TAP);
|
||||
|
||||
public static final int I_OVERLAP = 11;
|
||||
public static final int Q_OVERLAP = 23;
|
||||
|
||||
protected float[] mIBuffer = new float[FRAGMENT_SIZE + I_OVERLAP];
|
||||
protected float[] mQBuffer = new float[FRAGMENT_SIZE + Q_OVERLAP];
|
||||
protected short[] mSamples;
|
||||
protected int mSamplesPointer = 0;
|
||||
protected float mAverageDc;
|
||||
private long mTimestamp;
|
||||
private float mSamplesPerMillisecond;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param samples from the tuner.
|
||||
* @param residualI samples from previous buffer
|
||||
* @param residualQ samples from previous buffer
|
||||
* @param averageDc as measured
|
||||
* @param timestamp of the buffer
|
||||
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
|
||||
*/
|
||||
public SampleBufferIterator(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp,
|
||||
float samplesPerMillisecond)
|
||||
{
|
||||
if(residualI.length != I_OVERLAP || residualQ.length != Q_OVERLAP)
|
||||
{
|
||||
throw new IllegalArgumentException("Residual I length [" + residualI.length +
|
||||
"] must be " + I_OVERLAP + " and Residual Q length [" + residualQ.length +
|
||||
"] must be " + Q_OVERLAP);
|
||||
}
|
||||
|
||||
int requiredInterval = FRAGMENT_SIZE * 2; //requires 4 bytes (2 samples) per fragment
|
||||
|
||||
if(samples.length % requiredInterval != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Samples short array length [" + mSamples.length +
|
||||
"]must be an integer multiple of " + requiredInterval);
|
||||
}
|
||||
|
||||
mAverageDc = averageDc;
|
||||
mSamples = samples;
|
||||
mTimestamp = timestamp;
|
||||
mSamplesPerMillisecond = samplesPerMillisecond;
|
||||
|
||||
//Transfer and scale the residual I & Q samples from the previous buffer
|
||||
for(int i = 0; i < residualI.length; i++)
|
||||
{
|
||||
mIBuffer[i] = scale(residualI[i], averageDc);
|
||||
}
|
||||
|
||||
for(int q = 0; q < residualQ.length; q++)
|
||||
{
|
||||
mQBuffer[q] = scale(residualQ[q], averageDc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the timestamp for a samples buffer fragment based on where it is extracted from relative to the
|
||||
* native buffer starting timestamp.
|
||||
* @param samplesPointer for the start of the fragment. Note: this value will be divided by 2 to account for I/Q sample pairs.
|
||||
* @return timestamp adjusted to the fragment or sub-buffer start sample.
|
||||
*/
|
||||
protected long getFragmentTimestamp(int samplesPointer)
|
||||
{
|
||||
return mTimestamp + (long)(samplesPointer / 2 / mSamplesPerMillisecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return mSamplesPointer < mSamples.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales the short sample value to a 12-bit floating point and subtracts the average DC offset
|
||||
* @param value to scale
|
||||
* @param averageDc detected
|
||||
* @return scaled and normalized sample
|
||||
*/
|
||||
public static float scale(short value, float averageDc)
|
||||
{
|
||||
return ((float)value - 2048) * SCALE_SIGNED_12_BIT_TO_FLOAT - averageDc;
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Scalar implementation for non-packed native buffers
|
||||
*/
|
||||
public class SampleBufferIteratorScalar extends SampleBufferIterator<ComplexSamples>
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the device, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
|
||||
*/
|
||||
public SampleBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc, long timestamp, float samplesPerMillisecond)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
long timestamp = getFragmentTimestamp(mSamplesPointer);
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
mIBuffer[x + I_OVERLAP] = scale(mSamples[offset++], mAverageDc);
|
||||
mQBuffer[x + Q_OVERLAP] = scale(mSamples[offset++], mAverageDc);
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
float accumulator;
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = 0;
|
||||
|
||||
for(int tap = 0; tap < COEFFICIENTS.length; tap++)
|
||||
{
|
||||
accumulator += COEFFICIENTS[tap] * mQBuffer[x + tap];
|
||||
}
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator;
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator;
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import java.util.Arrays;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed native buffers
|
||||
*/
|
||||
public class SampleBufferIteratorVector128Bits extends SampleBufferIterator<ComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the device, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
|
||||
*/
|
||||
public SampleBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp, float samplesPerMillisecond)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
long timestamp = getFragmentTimestamp(mSamplesPointer);
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
|
||||
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
|
||||
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import java.util.Arrays;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed native buffers
|
||||
*/
|
||||
public class SampleBufferIteratorVector256Bits extends SampleBufferIterator<ComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the device, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
|
||||
*/
|
||||
public SampleBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp, float samplesPerMillisecond)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
long timestamp = getFragmentTimestamp(mSamplesPointer);
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import java.util.Arrays;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed native buffers
|
||||
*/
|
||||
public class SampleBufferIteratorVector512Bits extends SampleBufferIterator<ComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||
private float[] mFilterPart2;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the device, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
|
||||
*/
|
||||
public SampleBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp, float samplesPerMillisecond)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
|
||||
//Arrange the last 8 taps of the filter into the higher indices of an array to align for SIMD
|
||||
mFilterPart2 = new float[16];
|
||||
System.arraycopy(COEFFICIENTS, 16, mFilterPart2, 8, 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
long timestamp = getFragmentTimestamp(mSamplesPointer);
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, mFilterPart2, 0);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import java.util.Arrays;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed native buffers
|
||||
*/
|
||||
public class SampleBufferIteratorVector64Bits extends SampleBufferIterator<ComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the device, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
|
||||
*/
|
||||
public SampleBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp, float samplesPerMillisecond)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
long timestamp = getFragmentTimestamp(mSamplesPointer);
|
||||
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
//The i line is a simple delay line, so we'll just copy those as a new array here.
|
||||
float[] i = Arrays.copyOf(mIBuffer, FRAGMENT_SIZE);
|
||||
float[] q = new float[FRAGMENT_SIZE];
|
||||
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 2);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
|
||||
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 6);
|
||||
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 10);
|
||||
FloatVector f7 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
|
||||
FloatVector f8 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 14);
|
||||
FloatVector f9 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
FloatVector f10 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 18);
|
||||
FloatVector f11 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
|
||||
FloatVector f12 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 22);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 2), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
|
||||
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 6), accumulator);
|
||||
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 10), accumulator);
|
||||
accumulator = f7.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
|
||||
accumulator = f8.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 14), accumulator);
|
||||
accumulator = f9.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
accumulator = f10.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 18), accumulator);
|
||||
accumulator = f11.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
|
||||
accumulator = f12.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 22), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
q[x] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
i[x] = -i[x];
|
||||
q[x] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new ComplexSamples(i, q, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
|
||||
/**
|
||||
* Scalar implementation for non-packed native buffers
|
||||
*/
|
||||
public class SampleInterleavedBufferIteratorScalar extends SampleBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the device, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
|
||||
*/
|
||||
public SampleInterleavedBufferIteratorScalar(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp, float samplesPerMillisecond)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
long timestamp = getFragmentTimestamp(mSamplesPointer);
|
||||
int offset = mSamplesPointer;
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
mIBuffer[x + I_OVERLAP] = scale(mSamples[offset++], mAverageDc);
|
||||
mQBuffer[x + Q_OVERLAP] = scale(mSamples[offset++], mAverageDc);
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
|
||||
float accumulator;
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = 0;
|
||||
|
||||
for(int tap = 0; tap < COEFFICIENTS.length; tap++)
|
||||
{
|
||||
accumulator += COEFFICIENTS[tap] * mQBuffer[x + tap];
|
||||
}
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator;
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator;
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed native buffers
|
||||
*/
|
||||
public class SampleInterleavedBufferIteratorVector128Bits extends SampleBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the device, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
|
||||
*/
|
||||
public SampleInterleavedBufferIteratorVector128Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp, float samplesPerMillisecond)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
long timestamp = getFragmentTimestamp(mSamplesPointer);
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
|
||||
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
|
||||
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed native buffers
|
||||
*/
|
||||
public class SampleInterleavedBufferIteratorVector256Bits extends SampleBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the device, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
|
||||
*/
|
||||
public SampleInterleavedBufferIteratorVector256Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp, float samplesPerMillisecond)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
long timestamp = getFragmentTimestamp(mSamplesPointer);
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed native buffers
|
||||
*/
|
||||
public class SampleInterleavedBufferIteratorVector512Bits extends SampleBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||
private float[] mFilterPart2;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the device, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
|
||||
*/
|
||||
public SampleInterleavedBufferIteratorVector512Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp, float samplesPerMillisecond)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
|
||||
|
||||
//Arrange the last 8 taps of the filter into the higher indices of an array to align for SIMD
|
||||
mFilterPart2 = new float[16];
|
||||
System.arraycopy(COEFFICIENTS, 16, mFilterPart2, 8, 8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
long timestamp = getFragmentTimestamp(mSamplesPointer);
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, mFilterPart2, 0);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector SIMD implementation for non-packed native buffers
|
||||
*/
|
||||
public class SampleInterleavedBufferIteratorVector64Bits extends SampleBufferIterator<InterleavedComplexSamples>
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* @param samples from the device, either packed or unpacked.
|
||||
* @param residualI samples from last buffer
|
||||
* @param residualQ samples from last buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
* @param samplesPerMillisecond to calculate sub-buffer fragment timestamps
|
||||
*/
|
||||
public SampleInterleavedBufferIteratorVector64Bits(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp, float samplesPerMillisecond)
|
||||
{
|
||||
super(samples, residualI, residualQ, averageDc, timestamp, samplesPerMillisecond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedComplexSamples next()
|
||||
{
|
||||
if(mSamplesPointer >= mSamples.length)
|
||||
{
|
||||
throw new IllegalStateException("End of buffer exceeded");
|
||||
}
|
||||
|
||||
long timestamp = getFragmentTimestamp(mSamplesPointer);
|
||||
int offset = mSamplesPointer;
|
||||
int fragmentPointer = 0;
|
||||
float[] scaledSamples = new float[VECTOR_SPECIES.length()];
|
||||
|
||||
while(fragmentPointer < FRAGMENT_SIZE)
|
||||
{
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y++)
|
||||
{
|
||||
//Couldn't figure out an easy way to load as a short vector and recast to a float vector
|
||||
scaledSamples[y] = mSamples[offset++];
|
||||
}
|
||||
|
||||
//SIMD scaling operation
|
||||
FloatVector.fromArray(VECTOR_SPECIES, scaledSamples, 0)
|
||||
.sub(2048.0f)
|
||||
.mul(SCALE_SIGNED_12_BIT_TO_FLOAT)
|
||||
.sub(mAverageDc)
|
||||
.intoArray(scaledSamples, 0);
|
||||
|
||||
for(int y = 0; y < VECTOR_SPECIES.length(); y += 2)
|
||||
{
|
||||
mIBuffer[fragmentPointer + I_OVERLAP] = scaledSamples[y];
|
||||
mQBuffer[fragmentPointer++ + Q_OVERLAP] = scaledSamples[y + 1];
|
||||
}
|
||||
}
|
||||
|
||||
mSamplesPointer = offset;
|
||||
|
||||
float[] samples = new float[FRAGMENT_SIZE * 2];
|
||||
FloatVector accumulator;
|
||||
FloatVector f1 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 0);
|
||||
FloatVector f2 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 2);
|
||||
FloatVector f3 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 4);
|
||||
FloatVector f4 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 6);
|
||||
FloatVector f5 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 8);
|
||||
FloatVector f6 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 10);
|
||||
FloatVector f7 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 12);
|
||||
FloatVector f8 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 14);
|
||||
FloatVector f9 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 16);
|
||||
FloatVector f10 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 18);
|
||||
FloatVector f11 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 20);
|
||||
FloatVector f12 = FloatVector.fromArray(VECTOR_SPECIES, COEFFICIENTS, 22);
|
||||
|
||||
for(int x = 0; x < FRAGMENT_SIZE; x++)
|
||||
{
|
||||
accumulator = FloatVector.zero(VECTOR_SPECIES);
|
||||
accumulator = f1.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x), accumulator);
|
||||
accumulator = f2.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 2), accumulator);
|
||||
accumulator = f3.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 4), accumulator);
|
||||
accumulator = f4.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 6), accumulator);
|
||||
accumulator = f5.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 8), accumulator);
|
||||
accumulator = f6.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 10), accumulator);
|
||||
accumulator = f7.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 12), accumulator);
|
||||
accumulator = f8.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 14), accumulator);
|
||||
accumulator = f9.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 16), accumulator);
|
||||
accumulator = f10.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 18), accumulator);
|
||||
accumulator = f11.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 20), accumulator);
|
||||
accumulator = f12.fma(FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, x + 22), accumulator);
|
||||
|
||||
//Perform FS/2 frequency translation on final filtered values ... multiply sequence by 1, -1, etc.
|
||||
if(x % 2 == 0)
|
||||
{
|
||||
samples[2 * x] = mIBuffer[x];
|
||||
samples[2 * x + 1] = accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
else
|
||||
{
|
||||
samples[2 * x] = -mIBuffer[x];
|
||||
samples[2 * x + 1] = -accumulator.reduceLanes(VectorOperators.ADD);
|
||||
}
|
||||
}
|
||||
|
||||
//Copy residual end samples to beginning of buffers for the next iteration
|
||||
System.arraycopy(mIBuffer, FRAGMENT_SIZE, mIBuffer, 0, I_OVERLAP);
|
||||
System.arraycopy(mQBuffer, FRAGMENT_SIZE, mQBuffer, 0, Q_OVERLAP);
|
||||
|
||||
return new InterleavedComplexSamples(samples, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.buffer.sample;
|
||||
|
||||
import io.github.dsheirer.buffer.AbstractNativeBuffer;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Native buffer scalar implementation for non-packed samples.
|
||||
*/
|
||||
public class SampleNativeBuffer extends AbstractNativeBuffer
|
||||
{
|
||||
private final short[] mSamples;
|
||||
private final short[] mResidualI;
|
||||
private final short[] mResidualQ;
|
||||
private final float mAverageDc;
|
||||
private final Implementation mInterleavedImplementation;
|
||||
private final Implementation mNonInterleavedImplementation;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param samples (non-packed) from the device
|
||||
* @param residualI samples from previous buffer
|
||||
* @param residualQ samples from previous buffer
|
||||
* @param averageDc measured
|
||||
* @param timestamp of the buffer
|
||||
* @param interleavedImplementation optimal, scalar vs vector SIMD
|
||||
* @param nonInterleavedImplementation optimal, scalar vs vector SIMD
|
||||
* @param samplesPerMillisecond used to calculate sub-buffer fragment timestamp offsets from the start of this buffer.
|
||||
*/
|
||||
public SampleNativeBuffer(short[] samples, short[] residualI, short[] residualQ, float averageDc,
|
||||
long timestamp, Implementation interleavedImplementation,
|
||||
Implementation nonInterleavedImplementation, float samplesPerMillisecond)
|
||||
{
|
||||
super(timestamp, samplesPerMillisecond);
|
||||
|
||||
//Ensure we're an even multiple of the fragment size. Typically, this will be 64k or 128k
|
||||
if(samples.length % SampleBufferIterator.FRAGMENT_SIZE != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Samples short[] length [" + samples.length +
|
||||
"] must be an even multiple of " + SampleBufferIterator.FRAGMENT_SIZE);
|
||||
}
|
||||
|
||||
mSamples = samples;
|
||||
mResidualI = residualI;
|
||||
mResidualQ = residualQ;
|
||||
mAverageDc = averageDc;
|
||||
mInterleavedImplementation = interleavedImplementation;
|
||||
mNonInterleavedImplementation = nonInterleavedImplementation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sampleCount()
|
||||
{
|
||||
return mSamples.length / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ComplexSamples> iterator()
|
||||
{
|
||||
return switch(mNonInterleavedImplementation)
|
||||
{
|
||||
case VECTOR_SIMD_512 -> new SampleBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
|
||||
case VECTOR_SIMD_256-> new SampleBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
|
||||
case VECTOR_SIMD_128-> new SampleBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
|
||||
case VECTOR_SIMD_64 ->new SampleBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
|
||||
default -> new SampleBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<InterleavedComplexSamples> iteratorInterleaved()
|
||||
{
|
||||
return switch(mInterleavedImplementation)
|
||||
{
|
||||
case VECTOR_SIMD_512 -> new SampleInterleavedBufferIteratorVector512Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
|
||||
case VECTOR_SIMD_256-> new SampleInterleavedBufferIteratorVector256Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
|
||||
case VECTOR_SIMD_128-> new SampleInterleavedBufferIteratorVector128Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
|
||||
case VECTOR_SIMD_64 ->new SampleInterleavedBufferIteratorVector64Bits(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
|
||||
default -> new SampleInterleavedBufferIteratorScalar(mSamples, mResidualI, mResidualQ, mAverageDc, getTimestamp(), getSamplesPerMillisecond());
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.buffer.AbstractNativeBufferFactory;
|
||||
import io.github.dsheirer.buffer.INativeBuffer;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Implements a factory for creating SignedByteNativeBuffer instances
|
||||
*/
|
||||
public class SampleNativeBufferFactory extends AbstractNativeBufferFactory
|
||||
{
|
||||
private boolean mSamplePacking = false;
|
||||
private short[] mResidualI = new short[SampleBufferIterator.I_OVERLAP];
|
||||
private short[] mResidualQ = new short[SampleBufferIterator.Q_OVERLAP];
|
||||
private ISampleConverter mConverter;
|
||||
private Implementation mInterleavedIteratorImplementation;
|
||||
private Implementation mNonInterleavedIteratorImplementation;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public SampleNativeBufferFactory()
|
||||
{
|
||||
updateConverter();
|
||||
|
||||
mInterleavedIteratorImplementation = CalibrationManager.getInstance()
|
||||
.getImplementation(CalibrationType.SAMPLE_UNPACKED_INTERLEAVED_ITERATOR);
|
||||
mNonInterleavedIteratorImplementation = CalibrationManager.getInstance()
|
||||
.getImplementation(CalibrationType.SAMPLE_UNPACKED_ITERATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample packing places two 12-bit samples into 3 bytes when enabled or
|
||||
* places two 12-bit samples into 4 bytes when disabled.
|
||||
*
|
||||
* @param enabled
|
||||
*/
|
||||
public void setSamplePacking(boolean enabled)
|
||||
{
|
||||
mSamplePacking = enabled;
|
||||
updateConverter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an optimal sample converter instance.
|
||||
*/
|
||||
private void updateConverter()
|
||||
{
|
||||
if(mSamplePacking)
|
||||
{
|
||||
if(!(mConverter instanceof ScalarPackedSampleConverter))
|
||||
{
|
||||
mConverter = new ScalarPackedSampleConverter();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.SAMPLE_PACKED_CONVERTER);
|
||||
|
||||
if(implementation == Implementation.VECTOR_SIMD_PREFERRED)
|
||||
{
|
||||
if(!(mConverter instanceof VectorUnpackedSampleConverter))
|
||||
{
|
||||
mConverter = new VectorUnpackedSampleConverter();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!(mConverter instanceof ScalarUnpackedSampleConverter))
|
||||
{
|
||||
mConverter = new ScalarUnpackedSampleConverter();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public INativeBuffer getBuffer(ByteBuffer buffer, long timestamp)
|
||||
{
|
||||
short[] samples = mConverter.convert(buffer);
|
||||
|
||||
INativeBuffer nativeBuffer = new SampleNativeBuffer(samples,
|
||||
Arrays.copyOf(mResidualI, mResidualI.length),
|
||||
Arrays.copyOf(mResidualQ, mResidualQ.length), mConverter.getAverageDc(), timestamp,
|
||||
mInterleavedIteratorImplementation, mNonInterleavedIteratorImplementation, getSamplesPerMillisecond());
|
||||
|
||||
extractResidual(samples);
|
||||
|
||||
return nativeBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the residual overlap samples needed for continuity in the Hilbert transform filter.
|
||||
* @param samples to extract residual from
|
||||
*/
|
||||
private void extractResidual(short[] samples)
|
||||
{
|
||||
int offset = samples.length - (SampleBufferIterator.I_OVERLAP * 2);
|
||||
|
||||
for(int i = 0; i < SampleBufferIterator.I_OVERLAP; i++)
|
||||
{
|
||||
mResidualI[i] = samples[offset + (2 * i)];
|
||||
}
|
||||
|
||||
offset = samples.length - (SampleBufferIterator.Q_OVERLAP * 2) + 1;
|
||||
|
||||
for(int q = 0; q < SampleBufferIterator.Q_OVERLAP; q++)
|
||||
{
|
||||
mResidualQ[q] = samples[offset + (2 * q)];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.buffer.DcCorrectionManager;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Scalar implementation of sample converter for un-packed samples.
|
||||
*/
|
||||
public class ScalarPackedSampleConverter implements ISampleConverter
|
||||
{
|
||||
/**
|
||||
* Manages DC calculations and processing interval
|
||||
*/
|
||||
private DcCorrectionManager mDcCalculationManager = new DcCorrectionManager();
|
||||
|
||||
@Override
|
||||
public short[] convert(ByteBuffer buffer)
|
||||
{
|
||||
boolean shouldCalculateDc = mDcCalculationManager.shouldCalculateDc();
|
||||
|
||||
int offset = 0;
|
||||
short sample;
|
||||
short[] samples;
|
||||
byte b1, b2, b3;
|
||||
|
||||
samples = new short[buffer.capacity() / 3 * 2];
|
||||
|
||||
if(shouldCalculateDc)
|
||||
{
|
||||
long dcAccumulator = 0;
|
||||
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
b1 = buffer.get(offset++);
|
||||
b2 = buffer.get(offset++);
|
||||
b3 = buffer.get(offset++);
|
||||
|
||||
sample = (short)(((b1 & 0xFF) << 4) | ((b2 & 0xF0) >> 4));
|
||||
samples[x] = sample;
|
||||
dcAccumulator += sample;
|
||||
|
||||
sample = (short)(((b2 & 0xF) << 8) | (b3 & 0xFF));
|
||||
samples[x] = sample;
|
||||
dcAccumulator += sample;
|
||||
}
|
||||
|
||||
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
|
||||
averageDcNow *= SampleBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
|
||||
mDcCalculationManager.adjust(averageDcNow);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int x = 0; x < samples.length; x += 2)
|
||||
{
|
||||
b1 = buffer.get(offset++);
|
||||
b2 = buffer.get(offset++);
|
||||
b3 = buffer.get(offset++);
|
||||
|
||||
sample = (short)(((b1 & 0xFF) << 4) | ((b2 & 0xF0) >> 4));
|
||||
samples[x] = sample;
|
||||
|
||||
sample = (short)(((b2 & 0xF) << 8) | (b3 & 0xFF));
|
||||
samples[x] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAverageDc()
|
||||
{
|
||||
return mDcCalculationManager.getAverageDc();
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
byte[] raw = new byte[262144];
|
||||
ByteBuffer buffer = ByteBuffer.wrap(raw);
|
||||
|
||||
ScalarPackedSampleConverter scalar = new ScalarPackedSampleConverter();
|
||||
VectorUnpackedSampleConverter vector = new VectorUnpackedSampleConverter();
|
||||
|
||||
long accumulator = 0;
|
||||
int iterations = 1_000_000;
|
||||
|
||||
System.out.println("Starting ...");
|
||||
long start = System.currentTimeMillis();
|
||||
for(int count = 0; count < iterations; count++)
|
||||
{
|
||||
// short[] samples = scalar.convert(buffer);
|
||||
short[] samples = vector.convert(buffer);
|
||||
accumulator += samples[1];
|
||||
}
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
|
||||
System.out.println("Accumulator: " + accumulator);
|
||||
System.out.println("Elapsed: " + (elapsed / 1000.0f));
|
||||
System.out.println("Finished");
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.buffer.DcCorrectionManager;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Scalar implementation of sample converter for un-packed samples.
|
||||
*/
|
||||
public class ScalarUnpackedSampleConverter implements ISampleConverter
|
||||
{
|
||||
/**
|
||||
* Manages DC calculations and processing interval
|
||||
*/
|
||||
private DcCorrectionManager mDcCalculationManager = new DcCorrectionManager();
|
||||
|
||||
@Override
|
||||
public short[] convert(ByteBuffer buffer)
|
||||
{
|
||||
boolean shouldCalculateDc = mDcCalculationManager.shouldCalculateDc();
|
||||
|
||||
int offset = 0;
|
||||
short sample;
|
||||
short[] samples;
|
||||
byte b1, b2;
|
||||
|
||||
samples = new short[buffer.capacity() / 2];
|
||||
|
||||
if(shouldCalculateDc)
|
||||
{
|
||||
long dcAccumulator = 0;
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
b1 = buffer.get(offset++);
|
||||
b2 = buffer.get(offset++);
|
||||
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
|
||||
samples[x] = sample;
|
||||
dcAccumulator += sample;
|
||||
}
|
||||
|
||||
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
|
||||
averageDcNow *= SampleBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
|
||||
mDcCalculationManager.adjust(averageDcNow);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
b1 = buffer.get(offset++);
|
||||
b2 = buffer.get(offset++);
|
||||
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
|
||||
samples[x] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAverageDc()
|
||||
{
|
||||
return mDcCalculationManager.getAverageDc();
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General 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.sample;
|
||||
|
||||
import io.github.dsheirer.buffer.DcCorrectionManager;
|
||||
import java.nio.ByteBuffer;
|
||||
import jdk.incubator.vector.ShortVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Scalar implementation of sample converter for un-packed samples.
|
||||
*/
|
||||
public class VectorUnpackedSampleConverter implements ISampleConverter
|
||||
{
|
||||
private static final VectorSpecies<Short> VECTOR_SPECIES = ShortVector.SPECIES_PREFERRED;
|
||||
|
||||
/**
|
||||
* Manages DC calculations and processing interval
|
||||
*/
|
||||
private DcCorrectionManager mDcCalculationManager = new DcCorrectionManager();
|
||||
|
||||
@Override
|
||||
public short[] convert(ByteBuffer buffer)
|
||||
{
|
||||
boolean shouldCalculateDc = mDcCalculationManager.shouldCalculateDc();
|
||||
|
||||
short sample;
|
||||
short[] samples;
|
||||
byte b1, b2;
|
||||
|
||||
samples = new short[buffer.capacity() / 2];
|
||||
|
||||
int bytesOffset;
|
||||
int rawPointer = 0;
|
||||
ShortVector vector;
|
||||
|
||||
int samplesOffset = 0;
|
||||
|
||||
short[] bytes1 = new short[VECTOR_SPECIES.length()];
|
||||
short[] bytes2 = new short[VECTOR_SPECIES.length()];
|
||||
|
||||
if(shouldCalculateDc)
|
||||
{
|
||||
long dcAccumulator = 0;
|
||||
|
||||
for(; samplesOffset < VECTOR_SPECIES.loopBound(samples.length); samplesOffset += VECTOR_SPECIES.length())
|
||||
{
|
||||
bytesOffset = 0;
|
||||
|
||||
while(bytesOffset < VECTOR_SPECIES.length())
|
||||
{
|
||||
bytes1[bytesOffset] = (short)(buffer.get(rawPointer++) & 0xFF);
|
||||
bytes2[bytesOffset++] = (short)(buffer.get(rawPointer++) & 0x0F);
|
||||
}
|
||||
|
||||
vector = ShortVector.fromArray(VECTOR_SPECIES, bytes1, 0)
|
||||
.or(ShortVector.fromArray(VECTOR_SPECIES, bytes2, 0).lanewise(VectorOperators.LSHL, 8));
|
||||
dcAccumulator += vector.reduceLanes(VectorOperators.ADD);
|
||||
vector.intoArray(samples, samplesOffset);
|
||||
}
|
||||
|
||||
for(; samplesOffset < samples.length; samplesOffset++)
|
||||
{
|
||||
b1 = buffer.get(rawPointer++);
|
||||
b2 = buffer.get(rawPointer++);
|
||||
|
||||
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
|
||||
samples[samplesOffset] = sample;
|
||||
dcAccumulator += sample;
|
||||
}
|
||||
|
||||
float averageDcNow = ((float)dcAccumulator / (float)samples.length) - 2048.0f;
|
||||
averageDcNow *= SampleBufferIterator.SCALE_SIGNED_12_BIT_TO_FLOAT;
|
||||
mDcCalculationManager.adjust(averageDcNow);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(; samplesOffset < VECTOR_SPECIES.loopBound(samples.length); samplesOffset += VECTOR_SPECIES.length())
|
||||
{
|
||||
bytesOffset = 0;
|
||||
|
||||
while(bytesOffset < VECTOR_SPECIES.length())
|
||||
{
|
||||
bytes1[bytesOffset] = (short)(buffer.get(rawPointer++) & 0xFF);
|
||||
bytes2[bytesOffset++] = (short)(buffer.get(rawPointer++) & 0x0F);
|
||||
}
|
||||
|
||||
vector = ShortVector.fromArray(VECTOR_SPECIES, bytes1, 0)
|
||||
.or(ShortVector.fromArray(VECTOR_SPECIES, bytes2, 0).lanewise(VectorOperators.LSHL, 8));
|
||||
vector.intoArray(samples, samplesOffset);
|
||||
}
|
||||
|
||||
for(; samplesOffset < samples.length; samplesOffset++)
|
||||
{
|
||||
b1 = buffer.get(rawPointer++);
|
||||
b2 = buffer.get(rawPointer++);
|
||||
|
||||
sample = (short)(((b2 & 0x0F) << 8) | (b1 & 0xFF));
|
||||
samples[samplesOffset] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAverageDc()
|
||||
{
|
||||
return mDcCalculationManager.getAverageDc();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2024 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -429,7 +429,7 @@ public class ChannelMetadata implements Listener<IdentifierUpdateNotification>,
|
||||
{
|
||||
if(identifier.getForm() == Form.TALKER_ALIAS)
|
||||
{
|
||||
mTalkerAliasIdentifier = identifier;
|
||||
mTalkerAliasIdentifier = update.isAdd() ? identifier : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -79,7 +79,9 @@ public class ControllerPanel extends JPanel
|
||||
|
||||
private void init()
|
||||
{
|
||||
setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[]0[grow,fill]0[]"));
|
||||
//Disable visual padding. MigLayout otherwise assumes the tabbed pane has native Aqua margins on macOS and
|
||||
//expands it past the bounds of this panel, clipping the tab content on the left, right and bottom edges.
|
||||
setLayout(new MigLayout("insets 0 0 0 0, novisualpadding", "[grow,fill]", "[]0[grow,fill]0[]"));
|
||||
|
||||
add(mAudioPanel, "wrap");
|
||||
|
||||
|
||||
117
src/main/java/io/github/dsheirer/dsp/am/AmAudioNormalizer.java
Normal file
117
src/main/java/io/github/dsheirer/dsp/am/AmAudioNormalizer.java
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.dsp.am;
|
||||
|
||||
/**
|
||||
* Converts an AM envelope to audio by removing the carrier (DC) and scaling the audio by the carrier level.
|
||||
*
|
||||
* An AM signal's envelope is carrier * (1 + modulation), so using the carrier as the gain reference gives audio that
|
||||
* follows the modulation depth of the transmission, independent of the received signal strength and without the
|
||||
* pumping of an audio peak based AGC.
|
||||
*
|
||||
* The carrier estimate uses a cumulative mean at the start of a transmission to lock quickly and without a transient,
|
||||
* and then transitions to a low-pass filter. The DC removal follows the carrier estimate closely. The gain reference
|
||||
* rises with the carrier but falls slowly so that when the carrier drops, the remaining noise isn't amplified to full
|
||||
* scale before the squelch closes.
|
||||
*/
|
||||
public class AmAudioNormalizer
|
||||
{
|
||||
private static final double CARRIER_CUTOFF_HERTZ = 10.0;
|
||||
private static final double GAIN_DECAY_SECONDS = 2.0;
|
||||
|
||||
/**
|
||||
* Gain reference limit, approximately -80 dBFS, that sets the maximum applied gain.
|
||||
*/
|
||||
private static final float MINIMUM_CARRIER_LEVEL = 1E-4f;
|
||||
|
||||
/**
|
||||
* Audio amplitude produced by 100% modulation.
|
||||
*/
|
||||
private static final float FULL_MODULATION_AMPLITUDE = 0.75f;
|
||||
private static final float MAXIMUM_AMPLITUDE = 0.95f;
|
||||
|
||||
private float mCarrierAlpha;
|
||||
private float mGainDecayAlpha;
|
||||
private int mSettlingSamples;
|
||||
private int mSampleCount;
|
||||
private float mCarrier;
|
||||
private float mGainReference;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param sampleRate of the envelope samples in hertz
|
||||
*/
|
||||
public AmAudioNormalizer(double sampleRate)
|
||||
{
|
||||
setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sample rate and resets this normalizer.
|
||||
* @param sampleRate of the envelope samples in hertz
|
||||
*/
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
mCarrierAlpha = (float)(1.0 - Math.exp(-2.0 * Math.PI * CARRIER_CUTOFF_HERTZ / sampleRate));
|
||||
mGainDecayAlpha = (float)(1.0 - Math.exp(-1.0 / (GAIN_DECAY_SECONDS * sampleRate)));
|
||||
mSettlingSamples = (int)Math.ceil(1.0 / mCarrierAlpha);
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this normalizer to prepare for a new transmission.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mSampleCount = 0;
|
||||
mCarrier = 0.0f;
|
||||
mGainReference = 0.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the envelope sample to an audio sample.
|
||||
* @param envelope sample, unscaled
|
||||
* @return audio sample limited to the audio amplitude range.
|
||||
*/
|
||||
public float process(float envelope)
|
||||
{
|
||||
if(mSampleCount < mSettlingSamples)
|
||||
{
|
||||
mSampleCount++;
|
||||
mCarrier += (envelope - mCarrier) / mSampleCount;
|
||||
mGainReference = mCarrier;
|
||||
}
|
||||
else
|
||||
{
|
||||
mCarrier += (envelope - mCarrier) * mCarrierAlpha;
|
||||
|
||||
if(mCarrier > mGainReference)
|
||||
{
|
||||
mGainReference = mCarrier;
|
||||
}
|
||||
else
|
||||
{
|
||||
mGainReference += (mCarrier - mGainReference) * mGainDecayAlpha;
|
||||
}
|
||||
}
|
||||
|
||||
float audio = (envelope - mCarrier) / Math.max(mGainReference, MINIMUM_CARRIER_LEVEL) * FULL_MODULATION_AMPLITUDE;
|
||||
return Math.clamp(audio, -MAXIMUM_AMPLITUDE, MAXIMUM_AMPLITUDE);
|
||||
}
|
||||
}
|
||||
@@ -51,8 +51,6 @@ public class AmDemodulatorFactory
|
||||
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);
|
||||
|
||||
@@ -17,13 +17,14 @@
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.dsp.fm;
|
||||
package io.github.dsheirer.dsp.am;
|
||||
|
||||
import io.github.dsheirer.dsp.fm.IDemodulator;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
|
||||
/**
|
||||
* Interface for FM demodulator that provides squelch control.
|
||||
* Interface for AM demodulator that provides squelch control.
|
||||
*/
|
||||
public interface ISquelchingDemodulator extends IDemodulator
|
||||
{
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
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;
|
||||
@@ -32,32 +31,63 @@ import io.github.dsheirer.source.SourceEvent;
|
||||
public class SquelchingAMDemodulator implements ISquelchingDemodulator, Listener<SourceEvent>
|
||||
{
|
||||
private static final float ZERO = 0.0f;
|
||||
private static final float ENVELOPE_GAIN = 1.0f;
|
||||
private static final double DEFAULT_SAMPLE_RATE = 50000.0;
|
||||
private static final double FADE_SECONDS = 0.005;
|
||||
|
||||
/**
|
||||
* Audio delay, longer than the worst case 25 ms for the squelch to detect loss of signal plus the fade duration, so
|
||||
* that the audio fades out before the carrier drop reaches the audio normalizer, which would otherwise produce a
|
||||
* thump at the end of a transmission.
|
||||
*/
|
||||
private static final double AUDIO_DELAY_SECONDS = 0.032;
|
||||
private final IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
|
||||
private final IAmDemodulator mAmDemodulator;
|
||||
private final AdaptiveSquelch mAdaptiveSquelch;
|
||||
private final AmAudioNormalizer mAudioNormalizer = new AmAudioNormalizer(DEFAULT_SAMPLE_RATE);
|
||||
private float[] mDelayedEnvelope;
|
||||
private boolean[] mDelayedAudioEnabled;
|
||||
private int mDelayPointer = 0;
|
||||
private float mFadeGain = 0.0f;
|
||||
private float mFadeStep = (float)(1.0 / (DEFAULT_SAMPLE_RATE * FADE_SECONDS));
|
||||
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.
|
||||
* @param bandwidth of the channel in hertz
|
||||
*/
|
||||
public SquelchingAMDemodulator(float gain, float alpha, float squelchThreshold, boolean squelchAutoTrack)
|
||||
public SquelchingAMDemodulator(float squelchThreshold, boolean squelchAutoTrack, double bandwidth)
|
||||
{
|
||||
mAmDemodulator = AmDemodulatorFactory.getAmDemodulator(gain);
|
||||
mAdaptiveSquelch = new AdaptiveSquelch(alpha, squelchThreshold, squelchAutoTrack);
|
||||
mAmDemodulator = AmDemodulatorFactory.getAmDemodulator(ENVELOPE_GAIN);
|
||||
mAdaptiveSquelch = new AdaptiveSquelch(squelchThreshold, squelchAutoTrack, bandwidth);
|
||||
setAudioDelay(DEFAULT_SAMPLE_RATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or update the sample rate for the squelch to adjust the power level notification rate.
|
||||
* Set or update the sample rate for the squelch to adjust the power level notification rate and for the audio
|
||||
* normalizer timing.
|
||||
* @param sampleRate in hertz
|
||||
*/
|
||||
public void setSampleRate(int sampleRate)
|
||||
{
|
||||
mAdaptiveSquelch.setSampleRate(sampleRate);
|
||||
mAudioNormalizer.setSampleRate(sampleRate);
|
||||
mFadeStep = (float)Math.min(1.0, 1.0 / (sampleRate * FADE_SECONDS));
|
||||
setAudioDelay(sampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sizes the audio delay buffers.
|
||||
* @param sampleRate in hertz
|
||||
*/
|
||||
private void setAudioDelay(double sampleRate)
|
||||
{
|
||||
int length = (int)Math.ceil(sampleRate * AUDIO_DELAY_SECONDS);
|
||||
mDelayedEnvelope = new float[length];
|
||||
mDelayedAudioEnabled = new boolean[length];
|
||||
mDelayPointer = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,7 +102,7 @@ public class SquelchingAMDemodulator implements ISquelchingDemodulator, Listener
|
||||
* Demodulates the complex sample arrays.
|
||||
* @param i inphase sample array
|
||||
* @param q quadrature sample array
|
||||
* @return demodulated AM samples with gain applied.
|
||||
* @return demodulated audio samples, normalized to the carrier level, or zero valued samples while squelched.
|
||||
*/
|
||||
public float[] demodulate(float[] i, float[] q)
|
||||
{
|
||||
@@ -86,9 +116,26 @@ public class SquelchingAMDemodulator implements ISquelchingDemodulator, Listener
|
||||
{
|
||||
mAdaptiveSquelch.process(magnitude[x]);
|
||||
|
||||
if(!(mAdaptiveSquelch.isUnmuted()))
|
||||
boolean audioEnabled = mAdaptiveSquelch.isAudioEnabled();
|
||||
float envelope = mDelayedEnvelope[mDelayPointer];
|
||||
boolean delayedAudioEnabled = mDelayedAudioEnabled[mDelayPointer];
|
||||
mDelayedEnvelope[mDelayPointer] = demodulated[x];
|
||||
mDelayedAudioEnabled[mDelayPointer] = audioEnabled;
|
||||
mDelayPointer = (mDelayPointer + 1) % mDelayedEnvelope.length;
|
||||
|
||||
//Fade in once the delayed audio reaches the squelch open point and fade out as soon as the squelch detects
|
||||
//loss of signal, ahead of the delayed audio.
|
||||
mFadeGain = (audioEnabled && delayedAudioEnabled) ? Math.min(mFadeGain + mFadeStep, 1.0f) :
|
||||
Math.max(mFadeGain - mFadeStep, 0.0f);
|
||||
|
||||
if(mFadeGain > ZERO)
|
||||
{
|
||||
demodulated[x] = mAudioNormalizer.process(envelope) * mFadeGain;
|
||||
}
|
||||
else
|
||||
{
|
||||
demodulated[x] = ZERO;
|
||||
mAudioNormalizer.reset();
|
||||
}
|
||||
|
||||
if(mAdaptiveSquelch.isSquelchChanged())
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
*/
|
||||
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;
|
||||
@@ -32,7 +30,6 @@ 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.
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
*/
|
||||
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;
|
||||
@@ -32,7 +30,6 @@ 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.
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
*/
|
||||
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;
|
||||
@@ -32,7 +30,6 @@ 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.
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
*/
|
||||
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;
|
||||
@@ -32,7 +30,6 @@ 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.
|
||||
|
||||
@@ -257,6 +257,20 @@ public abstract class C4FMSymbolProcessor
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum bit errors allowed in a detected sync pattern while acquiring (ie not synchronized), measured from the
|
||||
* symbol decisions at the optimized symbol timing. Sync detections with more bit errors are rejected. Bit errors
|
||||
* include symbol magnitude (ie +3 vs +1) errors, so this rejects noise where the symbol signs correlate with the
|
||||
* sync pattern but the symbol magnitudes are inconsistent. Once synchronized, the limit isn't applied since the
|
||||
* sync detection timing is known and rejecting a valid sync detection costs the burst. Default implementation
|
||||
* imposes no limit.
|
||||
* @return maximum sync pattern bit error count while acquiring.
|
||||
*/
|
||||
public int getSyncAcquisitionMaxBitErrors()
|
||||
{
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of consecutive sync periods (capped at 3) where a sync was detected within the max message length.
|
||||
*/
|
||||
@@ -945,12 +959,14 @@ public abstract class C4FMSymbolProcessor
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this correction has a valid NID or the optimization (or detection) score is high quality.
|
||||
* Indicates if the optimization score is high quality and, while acquiring, the sync pattern bit errors don't
|
||||
* exceed the max.
|
||||
* @return true if valid.
|
||||
*/
|
||||
public boolean isValid()
|
||||
{
|
||||
return mOptimizationScore > getSyncThresholdEqualized();
|
||||
return mOptimizationScore > getSyncThresholdEqualized() &&
|
||||
(mSynchronized || mCorrectedBitCount <= getSyncAcquisitionMaxBitErrors());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,779 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*
|
||||
* The transform kernels in this class are ported from the mixed radix complex FFT (cffti, cfftf and passf2/3/4/5)
|
||||
* in JTransforms org.jtransforms.fft.FloatFFT_1D, which carries the following license:
|
||||
*
|
||||
* JTransforms
|
||||
* Copyright (c) 2007 onward, Piotr Wendykier
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package io.github.dsheirer.dsp.fft;
|
||||
|
||||
/**
|
||||
* Allocation-free mixed radix complex FFT for sizes that factor into 2, 3 and 5 only.
|
||||
*
|
||||
* This is a port of the JTransforms FloatFFT_1D mixed radix complex transform path. JTransforms allocates a new
|
||||
* 2N float scratch array on every mixed radix transform, which creates significant garbage collection pressure when
|
||||
* the transform runs at block rate (e.g. the polyphase channelizer IFFT). This implementation pre-allocates the
|
||||
* scratch buffer once and reuses it for each transform.
|
||||
*
|
||||
* Results match JTransforms FloatFFT_1D complexForward() and complexInverse(): the forward transform uses the
|
||||
* exp(-j...) kernel, the inverse uses exp(+j...), and the inverse optionally scales by 1/N. Twiddle factors are
|
||||
* calculated in double precision, so results can differ from JTransforms in the least significant bits.
|
||||
*
|
||||
* Samples are interleaved complex: a[offset + 2k] = Re[k] and a[offset + 2k + 1] = Im[k] for 0 <= k < N.
|
||||
*
|
||||
* Note: this class is NOT thread-safe since it reuses an internal scratch buffer. Use one instance per thread.
|
||||
*/
|
||||
public class MixedRadixComplexFFT
|
||||
{
|
||||
private static final int[] FACTORS = {4, 2, 3, 5};
|
||||
private static final int FORWARD = -1;
|
||||
private static final int INVERSE = 1;
|
||||
|
||||
private final int mSize;
|
||||
private final int[] mFactors;
|
||||
private final float[] mTwiddles;
|
||||
private final float[] mScratch;
|
||||
private final float mScale;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param size of the transform in complex samples. Must be a positive value with no prime factors other than
|
||||
* 2, 3 and 5.
|
||||
* @throws IllegalArgumentException if the size is not supported
|
||||
*/
|
||||
public MixedRadixComplexFFT(int size)
|
||||
{
|
||||
if(!isSupported(size))
|
||||
{
|
||||
throw new IllegalArgumentException("Unsupported FFT size [" + size +
|
||||
"] - size must be positive and have no prime factors other than 2, 3 and 5");
|
||||
}
|
||||
|
||||
mSize = size;
|
||||
mFactors = factorize(size);
|
||||
mTwiddles = new float[2 * size + 2];
|
||||
mScratch = new float[2 * size];
|
||||
mScale = 1.0f / size;
|
||||
calculateTwiddles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the size can be transformed by this class.
|
||||
* @param size in complex samples
|
||||
* @return true if the size is positive and has no prime factors other than 2, 3 and 5.
|
||||
*/
|
||||
public static boolean isSupported(int size)
|
||||
{
|
||||
if(size < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int remainder = size;
|
||||
|
||||
for(int factor: new int[]{2, 3, 5})
|
||||
{
|
||||
while(remainder % factor == 0)
|
||||
{
|
||||
remainder /= factor;
|
||||
}
|
||||
}
|
||||
|
||||
return remainder == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Size of this transform
|
||||
* @return size in complex samples
|
||||
*/
|
||||
public int getSize()
|
||||
{
|
||||
return mSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the forward DFT of the interleaved complex samples, in place. Equivalent to JTransforms
|
||||
* FloatFFT_1D.complexForward(samples, offset).
|
||||
* @param samples interleaved complex, with at least 2N values from the offset
|
||||
* @param offset to the first sample
|
||||
*/
|
||||
public void forward(float[] samples, int offset)
|
||||
{
|
||||
transform(samples, offset, FORWARD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the forward DFT of the interleaved complex samples, in place.
|
||||
* @param samples interleaved complex, with at least 2N values
|
||||
*/
|
||||
public void forward(float[] samples)
|
||||
{
|
||||
forward(samples, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the inverse DFT of the interleaved complex samples, in place. Equivalent to JTransforms
|
||||
* FloatFFT_1D.complexInverse(samples, offset, scale).
|
||||
* @param samples interleaved complex, with at least 2N values from the offset
|
||||
* @param offset to the first sample
|
||||
* @param scale true to scale the results by 1/N
|
||||
*/
|
||||
public void inverse(float[] samples, int offset, boolean scale)
|
||||
{
|
||||
transform(samples, offset, INVERSE);
|
||||
|
||||
if(scale && mSize > 1)
|
||||
{
|
||||
int end = offset + 2 * mSize;
|
||||
|
||||
for(int x = offset; x < end; x++)
|
||||
{
|
||||
samples[x] *= mScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the inverse DFT of the interleaved complex samples, in place.
|
||||
* @param samples interleaved complex, with at least 2N values
|
||||
* @param scale true to scale the results by 1/N
|
||||
*/
|
||||
public void inverse(float[] samples, boolean scale)
|
||||
{
|
||||
inverse(samples, 0, scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factors the size into the radix sequence used by the transform, ported from JTransforms cffti(). Radix 4 is
|
||||
* preferred, then 2, 3 and 5, and a radix 2 factor is moved to the front of the sequence.
|
||||
*/
|
||||
private static int[] factorize(int size)
|
||||
{
|
||||
if(size == 1)
|
||||
{
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
int[] factors = new int[32];
|
||||
int count = 0;
|
||||
int remainder = size;
|
||||
|
||||
for(int factor: FACTORS)
|
||||
{
|
||||
while(remainder % factor == 0)
|
||||
{
|
||||
factors[count++] = factor;
|
||||
remainder /= factor;
|
||||
|
||||
if(factor == 2 && count != 1)
|
||||
{
|
||||
System.arraycopy(factors, 0, factors, 1, count - 1);
|
||||
factors[0] = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(remainder != 1)
|
||||
{
|
||||
throw new IllegalArgumentException("Unsupported FFT size [" + size + "]");
|
||||
}
|
||||
|
||||
int[] result = new int[count];
|
||||
System.arraycopy(factors, 0, result, 0, count);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the twiddle factor table, ported from JTransforms cffti() with the table offset removed and the
|
||||
* angles calculated in double precision.
|
||||
*/
|
||||
private void calculateTwiddles()
|
||||
{
|
||||
if(mSize == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double angleIncrement = 2.0 * Math.PI / mSize;
|
||||
int i = 1;
|
||||
int l1 = 1;
|
||||
|
||||
for(int factor: mFactors)
|
||||
{
|
||||
int ld = 0;
|
||||
int l2 = l1 * factor;
|
||||
int ido = mSize / l2;
|
||||
int idot = ido + ido + 2;
|
||||
|
||||
for(int j = 1; j < factor; j++)
|
||||
{
|
||||
mTwiddles[i - 1] = 1;
|
||||
mTwiddles[i] = 0;
|
||||
ld += l1;
|
||||
int fi = 0;
|
||||
double argld = ld * angleIncrement;
|
||||
|
||||
for(int ii = 4; ii <= idot; ii += 2)
|
||||
{
|
||||
i += 2;
|
||||
fi++;
|
||||
double arg = fi * argld;
|
||||
mTwiddles[i - 1] = (float)Math.cos(arg);
|
||||
mTwiddles[i] = (float)Math.sin(arg);
|
||||
}
|
||||
}
|
||||
|
||||
l1 = l2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the transform, ported from JTransforms cfftf() using the reusable scratch buffer.
|
||||
* @param a samples
|
||||
* @param offa offset to first sample
|
||||
* @param isign -1 for forward and +1 for inverse
|
||||
*/
|
||||
private void transform(float[] a, int offa, int isign)
|
||||
{
|
||||
if(mSize == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float[] ch = mScratch;
|
||||
int twon = 2 * mSize;
|
||||
boolean inScratch = false;
|
||||
int l1 = 1;
|
||||
int iw = 0;
|
||||
|
||||
for(int factor: mFactors)
|
||||
{
|
||||
int l2 = factor * l1;
|
||||
int idot = 2 * (mSize / l2);
|
||||
|
||||
switch(factor)
|
||||
{
|
||||
case 4:
|
||||
if(inScratch)
|
||||
{
|
||||
passf4(idot, l1, ch, 0, a, offa, iw, isign);
|
||||
}
|
||||
else
|
||||
{
|
||||
passf4(idot, l1, a, offa, ch, 0, iw, isign);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if(inScratch)
|
||||
{
|
||||
passf2(idot, l1, ch, 0, a, offa, iw, isign);
|
||||
}
|
||||
else
|
||||
{
|
||||
passf2(idot, l1, a, offa, ch, 0, iw, isign);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if(inScratch)
|
||||
{
|
||||
passf3(idot, l1, ch, 0, a, offa, iw, isign);
|
||||
}
|
||||
else
|
||||
{
|
||||
passf3(idot, l1, a, offa, ch, 0, iw, isign);
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
if(inScratch)
|
||||
{
|
||||
passf5(idot, l1, ch, 0, a, offa, iw, isign);
|
||||
}
|
||||
else
|
||||
{
|
||||
passf5(idot, l1, a, offa, ch, 0, iw, isign);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported radix: " + factor);
|
||||
}
|
||||
|
||||
inScratch = !inScratch;
|
||||
l1 = l2;
|
||||
iw += (factor - 1) * idot;
|
||||
}
|
||||
|
||||
if(inScratch)
|
||||
{
|
||||
System.arraycopy(ch, 0, a, offa, twon);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Radix 2 butterfly, ported from JTransforms passf2().
|
||||
*/
|
||||
private void passf2(final int ido, final int l1, final float[] in, final int in_off, final float[] out,
|
||||
final int out_off, final int offset, final int isign)
|
||||
{
|
||||
final float[] wtable = mTwiddles;
|
||||
int iw1 = offset;
|
||||
int idx = ido * l1;
|
||||
|
||||
if(ido <= 2)
|
||||
{
|
||||
for(int k = 0; k < l1; k++)
|
||||
{
|
||||
int idx0 = k * ido;
|
||||
int iidx1 = in_off + 2 * idx0;
|
||||
int iidx2 = iidx1 + ido;
|
||||
float a1r = in[iidx1];
|
||||
float a1i = in[iidx1 + 1];
|
||||
float a2r = in[iidx2];
|
||||
float a2i = in[iidx2 + 1];
|
||||
|
||||
int oidx1 = out_off + idx0;
|
||||
int oidx2 = oidx1 + idx;
|
||||
out[oidx1] = a1r + a2r;
|
||||
out[oidx1 + 1] = a1i + a2i;
|
||||
out[oidx2] = a1r - a2r;
|
||||
out[oidx2 + 1] = a1i - a2i;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int k = 0; k < l1; k++)
|
||||
{
|
||||
for(int i = 0; i < ido - 1; i += 2)
|
||||
{
|
||||
int idx0 = k * ido;
|
||||
int iidx1 = in_off + i + 2 * idx0;
|
||||
int iidx2 = iidx1 + ido;
|
||||
float i1r = in[iidx1];
|
||||
float i1i = in[iidx1 + 1];
|
||||
float i2r = in[iidx2];
|
||||
float i2i = in[iidx2 + 1];
|
||||
|
||||
int widx1 = i + iw1;
|
||||
float w1r = wtable[widx1];
|
||||
float w1i = isign * wtable[widx1 + 1];
|
||||
|
||||
float t1r = i1r - i2r;
|
||||
float t1i = i1i - i2i;
|
||||
|
||||
int oidx1 = out_off + i + idx0;
|
||||
int oidx2 = oidx1 + idx;
|
||||
out[oidx1] = i1r + i2r;
|
||||
out[oidx1 + 1] = i1i + i2i;
|
||||
out[oidx2] = w1r * t1r - w1i * t1i;
|
||||
out[oidx2 + 1] = w1r * t1i + w1i * t1r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Radix 3 butterfly, ported from JTransforms passf3().
|
||||
*/
|
||||
private void passf3(final int ido, final int l1, final float[] in, final int in_off, final float[] out,
|
||||
final int out_off, final int offset, final int isign)
|
||||
{
|
||||
final float taur = -0.5f;
|
||||
final float taui = 0.866025403784438707610604524234076962f;
|
||||
final float[] wtable = mTwiddles;
|
||||
int iw1 = offset;
|
||||
int iw2 = iw1 + ido;
|
||||
final int idxt = l1 * ido;
|
||||
|
||||
if(ido == 2)
|
||||
{
|
||||
for(int k = 1; k <= l1; k++)
|
||||
{
|
||||
int iidx1 = in_off + (3 * k - 2) * ido;
|
||||
int iidx2 = iidx1 + ido;
|
||||
int iidx3 = iidx1 - ido;
|
||||
float i1r = in[iidx1];
|
||||
float i1i = in[iidx1 + 1];
|
||||
float i2r = in[iidx2];
|
||||
float i2i = in[iidx2 + 1];
|
||||
float i3r = in[iidx3];
|
||||
float i3i = in[iidx3 + 1];
|
||||
|
||||
float tr2 = i1r + i2r;
|
||||
float cr2 = i3r + taur * tr2;
|
||||
float ti2 = i1i + i2i;
|
||||
float ci2 = i3i + taur * ti2;
|
||||
float cr3 = isign * taui * (i1r - i2r);
|
||||
float ci3 = isign * taui * (i1i - i2i);
|
||||
|
||||
int oidx1 = out_off + (k - 1) * ido;
|
||||
int oidx2 = oidx1 + idxt;
|
||||
int oidx3 = oidx2 + idxt;
|
||||
out[oidx1] = i3r + tr2;
|
||||
out[oidx1 + 1] = i3i + ti2;
|
||||
out[oidx2] = cr2 - ci3;
|
||||
out[oidx2 + 1] = ci2 + cr3;
|
||||
out[oidx3] = cr2 + ci3;
|
||||
out[oidx3 + 1] = ci2 - cr3;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int k = 1; k <= l1; k++)
|
||||
{
|
||||
int idx1 = in_off + (3 * k - 2) * ido;
|
||||
int idx2 = out_off + (k - 1) * ido;
|
||||
|
||||
for(int i = 0; i < ido - 1; i += 2)
|
||||
{
|
||||
int iidx1 = i + idx1;
|
||||
int iidx2 = iidx1 + ido;
|
||||
int iidx3 = iidx1 - ido;
|
||||
float a1r = in[iidx1];
|
||||
float a1i = in[iidx1 + 1];
|
||||
float a2r = in[iidx2];
|
||||
float a2i = in[iidx2 + 1];
|
||||
float a3r = in[iidx3];
|
||||
float a3i = in[iidx3 + 1];
|
||||
|
||||
float tr2 = a1r + a2r;
|
||||
float cr2 = a3r + taur * tr2;
|
||||
float ti2 = a1i + a2i;
|
||||
float ci2 = a3i + taur * ti2;
|
||||
float cr3 = isign * taui * (a1r - a2r);
|
||||
float ci3 = isign * taui * (a1i - a2i);
|
||||
float dr2 = cr2 - ci3;
|
||||
float dr3 = cr2 + ci3;
|
||||
float di2 = ci2 + cr3;
|
||||
float di3 = ci2 - cr3;
|
||||
|
||||
int widx1 = i + iw1;
|
||||
int widx2 = i + iw2;
|
||||
float w1r = wtable[widx1];
|
||||
float w1i = isign * wtable[widx1 + 1];
|
||||
float w2r = wtable[widx2];
|
||||
float w2i = isign * wtable[widx2 + 1];
|
||||
|
||||
int oidx1 = i + idx2;
|
||||
int oidx2 = oidx1 + idxt;
|
||||
int oidx3 = oidx2 + idxt;
|
||||
out[oidx1] = a3r + tr2;
|
||||
out[oidx1 + 1] = a3i + ti2;
|
||||
out[oidx2] = w1r * dr2 - w1i * di2;
|
||||
out[oidx2 + 1] = w1r * di2 + w1i * dr2;
|
||||
out[oidx3] = w2r * dr3 - w2i * di3;
|
||||
out[oidx3 + 1] = w2r * di3 + w2i * dr3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Radix 4 butterfly, ported from JTransforms passf4().
|
||||
*/
|
||||
private void passf4(final int ido, final int l1, final float[] in, final int in_off, final float[] out,
|
||||
final int out_off, final int offset, final int isign)
|
||||
{
|
||||
final float[] wtable = mTwiddles;
|
||||
int iw1 = offset;
|
||||
int iw2 = iw1 + ido;
|
||||
int iw3 = iw2 + ido;
|
||||
int idx0 = l1 * ido;
|
||||
|
||||
if(ido == 2)
|
||||
{
|
||||
for(int k = 0; k < l1; k++)
|
||||
{
|
||||
int idxt1 = k * ido;
|
||||
int iidx1 = in_off + 4 * idxt1 + 1;
|
||||
int iidx2 = iidx1 + ido;
|
||||
int iidx3 = iidx2 + ido;
|
||||
int iidx4 = iidx3 + ido;
|
||||
|
||||
float i1i = in[iidx1 - 1];
|
||||
float i1r = in[iidx1];
|
||||
float i2i = in[iidx2 - 1];
|
||||
float i2r = in[iidx2];
|
||||
float i3i = in[iidx3 - 1];
|
||||
float i3r = in[iidx3];
|
||||
float i4i = in[iidx4 - 1];
|
||||
float i4r = in[iidx4];
|
||||
|
||||
float ti1 = i1r - i3r;
|
||||
float ti2 = i1r + i3r;
|
||||
float tr4 = i4r - i2r;
|
||||
float ti3 = i2r + i4r;
|
||||
float tr1 = i1i - i3i;
|
||||
float tr2 = i1i + i3i;
|
||||
float ti4 = i2i - i4i;
|
||||
float tr3 = i2i + i4i;
|
||||
|
||||
int oidx1 = out_off + idxt1;
|
||||
int oidx2 = oidx1 + idx0;
|
||||
int oidx3 = oidx2 + idx0;
|
||||
int oidx4 = oidx3 + idx0;
|
||||
out[oidx1] = tr2 + tr3;
|
||||
out[oidx1 + 1] = ti2 + ti3;
|
||||
out[oidx2] = tr1 + isign * tr4;
|
||||
out[oidx2 + 1] = ti1 + isign * ti4;
|
||||
out[oidx3] = tr2 - tr3;
|
||||
out[oidx3 + 1] = ti2 - ti3;
|
||||
out[oidx4] = tr1 - isign * tr4;
|
||||
out[oidx4 + 1] = ti1 - isign * ti4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int k = 0; k < l1; k++)
|
||||
{
|
||||
int idx1 = k * ido;
|
||||
int idx2 = in_off + 1 + 4 * idx1;
|
||||
|
||||
for(int i = 0; i < ido - 1; i += 2)
|
||||
{
|
||||
int iidx1 = i + idx2;
|
||||
int iidx2 = iidx1 + ido;
|
||||
int iidx3 = iidx2 + ido;
|
||||
int iidx4 = iidx3 + ido;
|
||||
float i1i = in[iidx1 - 1];
|
||||
float i1r = in[iidx1];
|
||||
float i2i = in[iidx2 - 1];
|
||||
float i2r = in[iidx2];
|
||||
float i3i = in[iidx3 - 1];
|
||||
float i3r = in[iidx3];
|
||||
float i4i = in[iidx4 - 1];
|
||||
float i4r = in[iidx4];
|
||||
|
||||
float ti1 = i1r - i3r;
|
||||
float ti2 = i1r + i3r;
|
||||
float ti3 = i2r + i4r;
|
||||
float tr4 = i4r - i2r;
|
||||
float tr1 = i1i - i3i;
|
||||
float tr2 = i1i + i3i;
|
||||
float ti4 = i2i - i4i;
|
||||
float tr3 = i2i + i4i;
|
||||
float cr3 = tr2 - tr3;
|
||||
float ci3 = ti2 - ti3;
|
||||
float cr2 = tr1 + isign * tr4;
|
||||
float cr4 = tr1 - isign * tr4;
|
||||
float ci2 = ti1 + isign * ti4;
|
||||
float ci4 = ti1 - isign * ti4;
|
||||
|
||||
int widx1 = i + iw1;
|
||||
int widx2 = i + iw2;
|
||||
int widx3 = i + iw3;
|
||||
float w1r = wtable[widx1];
|
||||
float w1i = isign * wtable[widx1 + 1];
|
||||
float w2r = wtable[widx2];
|
||||
float w2i = isign * wtable[widx2 + 1];
|
||||
float w3r = wtable[widx3];
|
||||
float w3i = isign * wtable[widx3 + 1];
|
||||
|
||||
int oidx1 = out_off + i + idx1;
|
||||
int oidx2 = oidx1 + idx0;
|
||||
int oidx3 = oidx2 + idx0;
|
||||
int oidx4 = oidx3 + idx0;
|
||||
out[oidx1] = tr2 + tr3;
|
||||
out[oidx1 + 1] = ti2 + ti3;
|
||||
out[oidx2] = w1r * cr2 - w1i * ci2;
|
||||
out[oidx2 + 1] = w1r * ci2 + w1i * cr2;
|
||||
out[oidx3] = w2r * cr3 - w2i * ci3;
|
||||
out[oidx3 + 1] = w2r * ci3 + w2i * cr3;
|
||||
out[oidx4] = w3r * cr4 - w3i * ci4;
|
||||
out[oidx4 + 1] = w3r * ci4 + w3i * cr4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Radix 5 butterfly, ported from JTransforms passf5().
|
||||
*/
|
||||
private void passf5(final int ido, final int l1, final float[] in, final int in_off, final float[] out,
|
||||
final int out_off, final int offset, final int isign)
|
||||
{
|
||||
final float tr11 = 0.309016994374947451262869435595348477f;
|
||||
final float ti11 = 0.951056516295153531181938433292089030f;
|
||||
final float tr12 = -0.809016994374947340240566973079694435f;
|
||||
final float ti12 = 0.587785252292473248125759255344746634f;
|
||||
final float[] wtable = mTwiddles;
|
||||
int iw1 = offset;
|
||||
int iw2 = iw1 + ido;
|
||||
int iw3 = iw2 + ido;
|
||||
int iw4 = iw3 + ido;
|
||||
int idx0 = l1 * ido;
|
||||
|
||||
if(ido == 2)
|
||||
{
|
||||
for(int k = 1; k <= l1; ++k)
|
||||
{
|
||||
int iidx1 = in_off + (5 * k - 4) * ido + 1;
|
||||
int iidx2 = iidx1 + ido;
|
||||
int iidx3 = iidx1 - ido;
|
||||
int iidx4 = iidx2 + ido;
|
||||
int iidx5 = iidx4 + ido;
|
||||
|
||||
float i1i = in[iidx1 - 1];
|
||||
float i1r = in[iidx1];
|
||||
float i2i = in[iidx2 - 1];
|
||||
float i2r = in[iidx2];
|
||||
float i3i = in[iidx3 - 1];
|
||||
float i3r = in[iidx3];
|
||||
float i4i = in[iidx4 - 1];
|
||||
float i4r = in[iidx4];
|
||||
float i5i = in[iidx5 - 1];
|
||||
float i5r = in[iidx5];
|
||||
|
||||
float ti5 = i1r - i5r;
|
||||
float ti2 = i1r + i5r;
|
||||
float ti4 = i2r - i4r;
|
||||
float ti3 = i2r + i4r;
|
||||
float tr5 = i1i - i5i;
|
||||
float tr2 = i1i + i5i;
|
||||
float tr4 = i2i - i4i;
|
||||
float tr3 = i2i + i4i;
|
||||
float cr2 = i3i + tr11 * tr2 + tr12 * tr3;
|
||||
float ci2 = i3r + tr11 * ti2 + tr12 * ti3;
|
||||
float cr3 = i3i + tr12 * tr2 + tr11 * tr3;
|
||||
float ci3 = i3r + tr12 * ti2 + tr11 * ti3;
|
||||
float cr5 = isign * (ti11 * tr5 + ti12 * tr4);
|
||||
float ci5 = isign * (ti11 * ti5 + ti12 * ti4);
|
||||
float cr4 = isign * (ti12 * tr5 - ti11 * tr4);
|
||||
float ci4 = isign * (ti12 * ti5 - ti11 * ti4);
|
||||
|
||||
int oidx1 = out_off + (k - 1) * ido;
|
||||
int oidx2 = oidx1 + idx0;
|
||||
int oidx3 = oidx2 + idx0;
|
||||
int oidx4 = oidx3 + idx0;
|
||||
int oidx5 = oidx4 + idx0;
|
||||
out[oidx1] = i3i + tr2 + tr3;
|
||||
out[oidx1 + 1] = i3r + ti2 + ti3;
|
||||
out[oidx2] = cr2 - ci5;
|
||||
out[oidx2 + 1] = ci2 + cr5;
|
||||
out[oidx3] = cr3 - ci4;
|
||||
out[oidx3 + 1] = ci3 + cr4;
|
||||
out[oidx4] = cr3 + ci4;
|
||||
out[oidx4 + 1] = ci3 - cr4;
|
||||
out[oidx5] = cr2 + ci5;
|
||||
out[oidx5 + 1] = ci2 - cr5;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int k = 1; k <= l1; k++)
|
||||
{
|
||||
int idx1 = in_off + 1 + (k * 5 - 4) * ido;
|
||||
int idx2 = out_off + (k - 1) * ido;
|
||||
|
||||
for(int i = 0; i < ido - 1; i += 2)
|
||||
{
|
||||
int iidx1 = i + idx1;
|
||||
int iidx2 = iidx1 + ido;
|
||||
int iidx3 = iidx1 - ido;
|
||||
int iidx4 = iidx2 + ido;
|
||||
int iidx5 = iidx4 + ido;
|
||||
float i1i = in[iidx1 - 1];
|
||||
float i1r = in[iidx1];
|
||||
float i2i = in[iidx2 - 1];
|
||||
float i2r = in[iidx2];
|
||||
float i3i = in[iidx3 - 1];
|
||||
float i3r = in[iidx3];
|
||||
float i4i = in[iidx4 - 1];
|
||||
float i4r = in[iidx4];
|
||||
float i5i = in[iidx5 - 1];
|
||||
float i5r = in[iidx5];
|
||||
|
||||
float ti5 = i1r - i5r;
|
||||
float ti2 = i1r + i5r;
|
||||
float ti4 = i2r - i4r;
|
||||
float ti3 = i2r + i4r;
|
||||
float tr5 = i1i - i5i;
|
||||
float tr2 = i1i + i5i;
|
||||
float tr4 = i2i - i4i;
|
||||
float tr3 = i2i + i4i;
|
||||
float cr2 = i3i + tr11 * tr2 + tr12 * tr3;
|
||||
float ci2 = i3r + tr11 * ti2 + tr12 * ti3;
|
||||
float cr3 = i3i + tr12 * tr2 + tr11 * tr3;
|
||||
float ci3 = i3r + tr12 * ti2 + tr11 * ti3;
|
||||
float cr5 = isign * (ti11 * tr5 + ti12 * tr4);
|
||||
float ci5 = isign * (ti11 * ti5 + ti12 * ti4);
|
||||
float cr4 = isign * (ti12 * tr5 - ti11 * tr4);
|
||||
float ci4 = isign * (ti12 * ti5 - ti11 * ti4);
|
||||
float dr3 = cr3 - ci4;
|
||||
float dr4 = cr3 + ci4;
|
||||
float di3 = ci3 + cr4;
|
||||
float di4 = ci3 - cr4;
|
||||
float dr5 = cr2 + ci5;
|
||||
float dr2 = cr2 - ci5;
|
||||
float di5 = ci2 - cr5;
|
||||
float di2 = ci2 + cr5;
|
||||
|
||||
int widx1 = i + iw1;
|
||||
int widx2 = i + iw2;
|
||||
int widx3 = i + iw3;
|
||||
int widx4 = i + iw4;
|
||||
float w1r = wtable[widx1];
|
||||
float w1i = isign * wtable[widx1 + 1];
|
||||
float w2r = wtable[widx2];
|
||||
float w2i = isign * wtable[widx2 + 1];
|
||||
float w3r = wtable[widx3];
|
||||
float w3i = isign * wtable[widx3 + 1];
|
||||
float w4r = wtable[widx4];
|
||||
float w4i = isign * wtable[widx4 + 1];
|
||||
|
||||
int oidx1 = i + idx2;
|
||||
int oidx2 = oidx1 + idx0;
|
||||
int oidx3 = oidx2 + idx0;
|
||||
int oidx4 = oidx3 + idx0;
|
||||
int oidx5 = oidx4 + idx0;
|
||||
out[oidx1] = i3i + tr2 + tr3;
|
||||
out[oidx1 + 1] = i3r + ti2 + ti3;
|
||||
out[oidx2] = w1r * dr2 - w1i * di2;
|
||||
out[oidx2 + 1] = w1r * di2 + w1i * dr2;
|
||||
out[oidx3] = w2r * dr3 - w2i * di3;
|
||||
out[oidx3 + 1] = w2r * di3 + w2i * dr3;
|
||||
out[oidx4] = w3r * dr4 - w3i * di4;
|
||||
out[oidx4 + 1] = w3r * di4 + w3i * dr4;
|
||||
out[oidx5] = w4r * dr5 - w4i * di5;
|
||||
out[oidx5 + 1] = w4r * di5 + w4i * dr5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.dsp.filter;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Designs low-pass filters for complex sample channels, with a check of the designed frequency response.
|
||||
*/
|
||||
public class ChannelFilterDesigner
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelFilterDesigner.class);
|
||||
private static final double MAXIMUM_STOP_BAND_DB = -40.0;
|
||||
private static final double MAXIMUM_PASS_BAND_DB = 1.0;
|
||||
private static final int FILTER_RESPONSE_CHECK_POINTS = 1024;
|
||||
private static final int FALLBACK_FILTER_SIZING_ATTENUATION_DB = 75;
|
||||
private static final int FALLBACK_FILTER_MAXIMUM_ATTENUATION_DB = 150;
|
||||
private static final int FALLBACK_FILTER_ATTENUATION_STEP_DB = 15;
|
||||
|
||||
/**
|
||||
* Designs the low-pass channel filter for complex samples, where the channel bandwidth spans the two-sided
|
||||
* (positive and negative frequency) signal. The stop band ripple provides approximately 46 dB of attenuation.
|
||||
*
|
||||
* The remez designer can converge on a filter that meets the specification at the design grid points but has a large
|
||||
* response spike outside of them, so the response of the designed filter is checked before it's used.
|
||||
*
|
||||
* @param sampleRate of the samples to filter in hertz
|
||||
* @param bandwidth of the channel in hertz
|
||||
* @return filter coefficients
|
||||
*/
|
||||
public static float[] design(double sampleRate, double bandwidth)
|
||||
{
|
||||
int passBandStop = (int)(bandwidth * .4);
|
||||
int stopBandStart = (int)(bandwidth * .5);
|
||||
|
||||
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
|
||||
.sampleRate(sampleRate)
|
||||
.gridDensity(16)
|
||||
.oddLength(true)
|
||||
.passBandCutoff(passBandStop)
|
||||
.passBandAmplitude(1.0)
|
||||
.passBandRipple(0.01)
|
||||
.stopBandStart(stopBandStart)
|
||||
.stopBandAmplitude(0.0)
|
||||
.stopBandRipple(0.005)
|
||||
.build();
|
||||
|
||||
float[] coefficients = null;
|
||||
|
||||
try
|
||||
{
|
||||
coefficients = FilterFactory.getTaps(specification);
|
||||
}
|
||||
catch(FilterDesignException fde)
|
||||
{
|
||||
LOGGER.warn("Couldn't design remez channel filter for sample rate [" + sampleRate +
|
||||
"] pass frequency [" + passBandStop + "] and stop frequency [" + stopBandStart + "]");
|
||||
}
|
||||
|
||||
if(coefficients == null || !isValidFilterResponse(coefficients, sampleRate, passBandStop, stopBandStart))
|
||||
{
|
||||
LOGGER.info("Unable to use remez filter designer for sample rate [" + sampleRate + "] pass band stop [" +
|
||||
passBandStop + "] and stop band start [" + stopBandStart +
|
||||
"] - will proceed using simple low pass filter design");
|
||||
//Center the cutoff so the bandwidth matches the remez design and size the filter to meet the response limits.
|
||||
for(int attenuation = FALLBACK_FILTER_SIZING_ATTENUATION_DB; attenuation <= FALLBACK_FILTER_MAXIMUM_ATTENUATION_DB;
|
||||
attenuation += FALLBACK_FILTER_ATTENUATION_STEP_DB)
|
||||
{
|
||||
int length = FilterFactory.getLowPass(sampleRate, passBandStop, stopBandStart, attenuation,
|
||||
WindowType.HAMMING, true).length;
|
||||
coefficients = FilterFactory.getLowPass(sampleRate, (passBandStop + stopBandStart) / 2, length, WindowType.HAMMING);
|
||||
|
||||
if(isValidFilterResponse(coefficients, sampleRate, passBandStop, stopBandStart))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the frequency response of the low-pass filter, relative to the response at DC.
|
||||
* @param coefficients of the filter
|
||||
* @param sampleRate of the filter in hertz
|
||||
* @param passBandStop frequency in hertz
|
||||
* @param stopBandStart frequency in hertz
|
||||
* @return true if the pass band and stop band responses are within limits, through to half the sample rate.
|
||||
*/
|
||||
public static boolean isValidFilterResponse(float[] coefficients, double sampleRate, int passBandStop, int stopBandStart)
|
||||
{
|
||||
double dc = getResponseDb(coefficients, 0.0, sampleRate);
|
||||
double nyquist = sampleRate / 2.0;
|
||||
|
||||
for(int x = 0; x <= FILTER_RESPONSE_CHECK_POINTS; x++)
|
||||
{
|
||||
double frequency = nyquist * x / FILTER_RESPONSE_CHECK_POINTS;
|
||||
double response = getResponseDb(coefficients, frequency, sampleRate) - dc;
|
||||
|
||||
if((frequency <= passBandStop && response > MAXIMUM_PASS_BAND_DB) ||
|
||||
(frequency >= stopBandStart && response > MAXIMUM_STOP_BAND_DB))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the magnitude response of the filter at the specified frequency.
|
||||
*/
|
||||
public static double getResponseDb(float[] coefficients, double frequency, double sampleRate)
|
||||
{
|
||||
double radians = 2.0 * Math.PI * frequency / sampleRate;
|
||||
double real = 0.0;
|
||||
double imaginary = 0.0;
|
||||
|
||||
for(int x = 0; x < coefficients.length; x++)
|
||||
{
|
||||
real += coefficients[x] * Math.cos(radians * x);
|
||||
imaginary += coefficients[x] * Math.sin(radians * x);
|
||||
}
|
||||
|
||||
return 20.0 * Math.log10(Math.hypot(real, imaginary));
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package io.github.dsheirer.dsp.filter.channelizer;
|
||||
|
||||
import io.github.dsheirer.dsp.fft.MixedRadixComplexFFT;
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.sample.complex.InterleavedComplexSamples;
|
||||
@@ -27,7 +28,6 @@ import java.util.Arrays;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.jtransforms.fft.FloatFFT_1D;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -85,7 +85,8 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
|
||||
//Sized to process 40 times per second
|
||||
private IFFTProcessorDispatcher mIFFTProcessorDispatcher = new IFFTProcessorDispatcher(25);
|
||||
private FloatFFT_1D mFFT;
|
||||
//Allocation-free IFFT. Only used by the IFFT processor dispatcher thread since it reuses a scratch buffer.
|
||||
private MixedRadixComplexFFT mFFT;
|
||||
private ChannelDFT mChannelDFT;
|
||||
private float[] mInlineSamples;
|
||||
private float[] mInlineFilter;
|
||||
@@ -453,7 +454,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
private void init(float[] coefficients)
|
||||
{
|
||||
int channelCount = getChannelCount();
|
||||
mFFT = new FloatFFT_1D(channelCount);
|
||||
mFFT = new MixedRadixComplexFFT(channelCount);
|
||||
mChannelDFT = ChannelDFTFactory.getChannelDFT(channelCount);
|
||||
int bufferLength = getSubChannelCount() * mTapsPerChannel;
|
||||
mSamplesPerBlock = channelCount; //Same as subChannelCount / 2
|
||||
@@ -505,7 +506,7 @@ public class ComplexPolyphaseChannelizerM2 extends AbstractComplexPolyphaseChann
|
||||
for(int offset = 0; offset < samples.length; offset += blockLength)
|
||||
{
|
||||
//Rotate each of the channels to the correct phase using the IFFT
|
||||
mFFT.complexInverse(samples, offset, true);
|
||||
mFFT.inverse(samples, offset, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import io.github.dsheirer.dsp.filter.channelizer.output.OneChannelOutputProcesso
|
||||
import io.github.dsheirer.dsp.filter.channelizer.output.TwoChannelOutputProcessor;
|
||||
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.SerializedListener;
|
||||
import io.github.dsheirer.sample.complex.ComplexSamples;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.source.tuner.channel.TunerChannel;
|
||||
@@ -45,7 +46,8 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
{
|
||||
private Logger mLog = LoggerFactory.getLogger(PolyphaseChannelSource.class);
|
||||
private IPolyphaseChannelOutputProcessor mPolyphaseChannelOutputProcessor;
|
||||
private Listener<ComplexSamples> mSamplesListener;
|
||||
//Output processor replacement can overlap the outgoing and incoming dispatcher threads, and consumers aren't thread safe
|
||||
private final SerializedListener<ComplexSamples> mSamplesListener = new SerializedListener<>();
|
||||
private double mChannelSampleRate;
|
||||
private long mIndexCenterFrequency;
|
||||
private List<Integer> mOutputProcessorIndexes = new ArrayList<>();
|
||||
@@ -148,23 +150,20 @@ public class PolyphaseChannelSource extends TunerChannelSource implements Listen
|
||||
@Override
|
||||
public void setListener(final Listener<ComplexSamples> listener)
|
||||
{
|
||||
mSamplesListener = listener;
|
||||
mSamplesListener.setListener(listener);
|
||||
mPolyphaseChannelOutputProcessor.setListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(ComplexSamples complexSamples)
|
||||
{
|
||||
if(mSamplesListener != null)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
mSamplesListener.receive(complexSamples);
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error dispatching complex samples to listener [" + mSamplesListener.getClass() + "]", t);
|
||||
}
|
||||
mSamplesListener.receive(complexSamples);
|
||||
}
|
||||
catch(Throwable t)
|
||||
{
|
||||
mLog.error("Error dispatching complex samples to listener", t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ public class RemezFIRFilterDesigner
|
||||
}
|
||||
catch( FilterDesignException fde )
|
||||
{
|
||||
mLog.error( "Filter design error - couldn't find extremal indices at count [" +
|
||||
mLog.warn( "Filter design error - couldn't find extremal indices at count [" +
|
||||
iterationCount + "] try changing filter order up/down from [" +
|
||||
mSpecification.getOrder() + "]" );
|
||||
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,66 +19,106 @@
|
||||
|
||||
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 java.util.Arrays;
|
||||
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.
|
||||
* Adaptive power squelch control that optionally tracks the noise floor and auto-adjusts squelch threshold.
|
||||
*
|
||||
* Signal power is measured in 5 millisecond blocks so that all timing is independent of the sample rate. The squelch
|
||||
* opens after consecutive blocks at or above the threshold. It closes on a 20 millisecond power average that spans at
|
||||
* least one cycle of the lowest voice frequencies, so that modulation doesn't look like a loss of signal, and which
|
||||
* responds in a fixed time regardless of how strong the signal was.
|
||||
*
|
||||
* Closing occurs in two steps. Audio is disabled as soon as the power average falls below the close threshold, which
|
||||
* limits the noise tail, while the squelch stays open for a hang period to bridge a brief signal fade without ending
|
||||
* the call.
|
||||
*
|
||||
* When auto-track is enabled, the noise floor is the lowest power from the recent intervals that look like noise, using
|
||||
* the ratio mean(power^2) / mean(power)^2, which is 2.0 for gaussian noise and approaches 1.0 for a steady carrier. The
|
||||
* threshold is held at a margin over the noise floor, so a threshold below the noise floor can't latch the squelch open.
|
||||
* The squelch stays closed until the first noise floor measurement is complete. A signal that doesn't look like noise
|
||||
* is excluded from the noise floor unless it persists for the carrier timeout, after which it's treated as the noise
|
||||
* floor so that a dead carrier doesn't hold the squelch open. The noise floor recovers when the carrier ends. A carrier
|
||||
* with voice modulation, such as a continuous broadcast, doesn't time out, and a timed out carrier that develops voice
|
||||
* modulation is removed from the noise floor.
|
||||
*
|
||||
* 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 static final double DEFAULT_SAMPLE_RATE = 50000.0;
|
||||
private static final double BLOCK_SECONDS = 0.005;
|
||||
private static final int OPEN_BLOCKS = 2; //10 milliseconds
|
||||
private static final int CLOSE_AVERAGE_BLOCKS = 4; //20 milliseconds
|
||||
private static final int HANG_BLOCKS = 20; //100 milliseconds
|
||||
private static final int POWER_INTERVAL_BLOCKS = 100; //500 milliseconds
|
||||
private static final int NOISE_WINDOW_INTERVALS = 20; //10 seconds
|
||||
private static final int CARRIER_TIMEOUT_INTERVALS = 120; //60 seconds
|
||||
private static final int MINIMUM_VOICE_INTERVALS = 3;
|
||||
private static final double NOISE_POWER_RATIO = 1.85;
|
||||
private static final float MINIMUM_SQUELCH_MARGIN = 4.0f; //6 dB
|
||||
private static final float CLOSE_THRESHOLD_RATIO = 0.5f; //3 dB below the open threshold
|
||||
private final float[] mCloseBlocks = new float[CLOSE_AVERAGE_BLOCKS];
|
||||
private int mCloseBlockPointer = 0;
|
||||
private float mCloseAccumulator = 0.0f;
|
||||
private int mBlockSize = (int)Math.round(DEFAULT_SAMPLE_RATE * BLOCK_SECONDS);
|
||||
private int mBlockSampleCount = 0;
|
||||
private float mBlockAccumulator = 0.0f;
|
||||
private int mOpenCount = 0;
|
||||
private int mHangCount = 0;
|
||||
private float mPower = 0.0f;
|
||||
private boolean mSquelch;
|
||||
private float mSquelchThreshold;
|
||||
private double mIntervalPower = 0.0;
|
||||
private double mIntervalPowerSquared = 0.0;
|
||||
private int mIntervalSampleCount = 0;
|
||||
private int mIntervalBlockCount = 0;
|
||||
private boolean mFirstIntervalComplete = false;
|
||||
private final float[] mNoiseHistory = new float[NOISE_WINDOW_INTERVALS];
|
||||
private int mNoiseHistoryPointer = 0;
|
||||
private int mNoiseHistoryCount = 0;
|
||||
private int mCarrierIntervalCount = 0;
|
||||
private int mVoiceIntervalCount = 0;
|
||||
private boolean mCarrierTimedOut = false;
|
||||
private boolean mCarrierDetected = false;
|
||||
private Float mNoiseFloorBeforeCarrier;
|
||||
private final VoiceModulationDetector mVoiceModulationDetector;
|
||||
private Float mNoiseFloor;
|
||||
private float mSquelchBuffer;
|
||||
private Float mLowestNoiseFloor;
|
||||
private float mUserThreshold;
|
||||
private boolean mSquelch = true;
|
||||
private float mSquelchThreshold;
|
||||
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.
|
||||
* @param bandwidth of the channel in hertz
|
||||
*/
|
||||
public AdaptiveSquelch(float alpha, float squelchThreshold, boolean autoTrack)
|
||||
public AdaptiveSquelch(float squelchThreshold, boolean autoTrack, double bandwidth)
|
||||
{
|
||||
mFilter = new SinglePoleIirFilter(alpha);
|
||||
mVoiceModulationDetector = new VoiceModulationDetector(DEFAULT_SAMPLE_RATE, bandwidth,
|
||||
(int)Math.round(DEFAULT_SAMPLE_RATE * BLOCK_SECONDS) * POWER_INTERVAL_BLOCKS);
|
||||
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.
|
||||
* Sets the sample rate to size the power measurement blocks.
|
||||
* @param sampleRate in hertz
|
||||
*/
|
||||
public void setSampleRate(int sampleRate)
|
||||
{
|
||||
mPowerLevelBroadcastThreshold = sampleRate / 2;
|
||||
mBlockSize = Math.max(1, (int)Math.round(sampleRate * BLOCK_SECONDS));
|
||||
mVoiceModulationDetector.configure(sampleRate, mBlockSize * POWER_INTERVAL_BLOCKS);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,32 +172,36 @@ public class AdaptiveSquelch implements Listener<SourceEvent>
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the squelch threshold
|
||||
* Sets the squelch threshold. With auto-track enabled, the threshold is treated as a margin over the current noise
|
||||
* floor, subject to the minimum squelch margin. The margin is stored relative to the lowest observed noise floor so
|
||||
* that a change made while a timed out carrier has raised the noise floor applies once that carrier ends.
|
||||
* @param squelchThreshold in decibels
|
||||
*/
|
||||
public void setSquelchThreshold(float squelchThreshold)
|
||||
{
|
||||
float squelchThresholdDb = fromDb(squelchThreshold);
|
||||
setSquelchThreshold(squelchThresholdDb, true);
|
||||
float threshold = fromDb(squelchThreshold);
|
||||
|
||||
if(mAutoTrackNoiseFloor && mNoiseFloor != null)
|
||||
{
|
||||
mUserThreshold = mLowestNoiseFloor * Math.max(MINIMUM_SQUELCH_MARGIN, threshold / mNoiseFloor);
|
||||
updateTrackedThreshold();
|
||||
}
|
||||
else
|
||||
{
|
||||
mUserThreshold = threshold;
|
||||
applySquelchThreshold(mUserThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the squelch threshold. This method is synchronized to coordinate changes requested by the user/UI thread
|
||||
* Applies 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)
|
||||
private synchronized void applySquelchThreshold(float squelchThreshold)
|
||||
{
|
||||
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()));
|
||||
}
|
||||
|
||||
@@ -183,59 +227,231 @@ public class AdaptiveSquelch implements Listener<SourceEvent>
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a complex IQ sample and changes squelch state when the signal power is above or below the
|
||||
* threshold value.
|
||||
* Processes a complex IQ sample and updates the squelch state at the end of each power measurement block.
|
||||
* @param magnitude of a complex sample (inphase * inphase + quadrature * quadrature)
|
||||
*/
|
||||
public void process(float magnitude)
|
||||
{
|
||||
mPower = mFilter.filter(magnitude);
|
||||
mBlockAccumulator += magnitude;
|
||||
mBlockSampleCount++;
|
||||
mIntervalPower += magnitude;
|
||||
mIntervalPowerSquared += (double)magnitude * magnitude;
|
||||
mIntervalSampleCount++;
|
||||
|
||||
if(mSquelch && mPower >= mSquelchThreshold)
|
||||
//Voice detection is only needed for a carrier, so that channels with only noise don't incur this cost.
|
||||
if(mCarrierDetected)
|
||||
{
|
||||
setSquelch(false);
|
||||
}
|
||||
else if(!mSquelch && mPower < mSquelchThreshold)
|
||||
{
|
||||
setSquelch(true);
|
||||
mVoiceModulationDetector.receive((float)Math.sqrt(magnitude));
|
||||
}
|
||||
|
||||
mPowerLevelBroadcastCount++;
|
||||
if(mPowerLevelBroadcastCount % mPowerLevelBroadcastThreshold == 0)
|
||||
if(mBlockSampleCount >= mBlockSize)
|
||||
{
|
||||
mPowerLevelBroadcastCount = 0;
|
||||
broadcast(SourceEvent.channelPowerLevel(null, 10.0 * Math.log10(mPower)));
|
||||
|
||||
if(mSquelch)
|
||||
{
|
||||
adjustSquelchThreshold();
|
||||
}
|
||||
float power = mBlockAccumulator / mBlockSampleCount;
|
||||
mBlockAccumulator = 0.0f;
|
||||
mBlockSampleCount = 0;
|
||||
processBlock(power);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Updates the squelch state from a block power measurement.
|
||||
* @param power of the block
|
||||
*/
|
||||
private void adjustSquelchThreshold()
|
||||
private void processBlock(float power)
|
||||
{
|
||||
if(mAutoTrackNoiseFloor)
|
||||
mCloseAccumulator += power - mCloseBlocks[mCloseBlockPointer];
|
||||
mCloseBlocks[mCloseBlockPointer] = power;
|
||||
mCloseBlockPointer = (mCloseBlockPointer + 1) % CLOSE_AVERAGE_BLOCKS;
|
||||
|
||||
if(mSquelch)
|
||||
{
|
||||
if(mNoiseFloor == null)
|
||||
//With auto-track, the threshold isn't reliable until there's a noise floor measurement.
|
||||
boolean ready = mFirstIntervalComplete || !mAutoTrackNoiseFloor;
|
||||
mOpenCount = (ready && power >= mSquelchThreshold) ? mOpenCount + 1 : 0;
|
||||
|
||||
if(mOpenCount >= OPEN_BLOCKS)
|
||||
{
|
||||
//We set the squelch buffer just once, after the squelch threshold is set and noise floor is set to null
|
||||
mSquelchBuffer = mSquelchThreshold - mPower;
|
||||
mOpenCount = 0;
|
||||
mHangCount = 0;
|
||||
//Seed the close average so that it doesn't start with the blocks from before the signal arrived.
|
||||
Arrays.fill(mCloseBlocks, power);
|
||||
mCloseAccumulator = power * CLOSE_AVERAGE_BLOCKS;
|
||||
setSquelch(false);
|
||||
}
|
||||
}
|
||||
else if(mCloseAccumulator / CLOSE_AVERAGE_BLOCKS >= mSquelchThreshold * CLOSE_THRESHOLD_RATIO)
|
||||
{
|
||||
mHangCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
mHangCount++;
|
||||
|
||||
if(mHangCount >= HANG_BLOCKS)
|
||||
{
|
||||
setSquelch(true);
|
||||
}
|
||||
}
|
||||
|
||||
updatePowerInterval();
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts power blocks to provide periodic power level notifications and noise floor updates.
|
||||
*/
|
||||
private void updatePowerInterval()
|
||||
{
|
||||
mIntervalBlockCount++;
|
||||
|
||||
if(mIntervalBlockCount >= POWER_INTERVAL_BLOCKS)
|
||||
{
|
||||
double mean = mIntervalPower / mIntervalSampleCount;
|
||||
mPower = (float)mean;
|
||||
broadcast(SourceEvent.channelPowerLevel(null, 10.0 * Math.log10(mPower)));
|
||||
|
||||
if(mAutoTrackNoiseFloor && mean > 0.0)
|
||||
{
|
||||
updateNoiseFloor(mIntervalPowerSquared / mIntervalSampleCount / (mean * mean));
|
||||
}
|
||||
|
||||
mNoiseFloor = mPower;
|
||||
mFirstIntervalComplete = true;
|
||||
mIntervalPower = 0.0;
|
||||
mIntervalPowerSquared = 0.0;
|
||||
mIntervalSampleCount = 0;
|
||||
mIntervalBlockCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if((System.currentTimeMillis() - mLastAutoAdjustTimestamp) > AUTO_ADJUST_PERIOD_MS)
|
||||
{
|
||||
setSquelchThreshold(mNoiseFloor + mSquelchBuffer, false);
|
||||
mLastAutoAdjustTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
/**
|
||||
* Updates the noise floor history from the current interval power and updates the squelch threshold.
|
||||
* @param powerRatio mean(power^2) / mean(power)^2 for the interval
|
||||
*/
|
||||
private void updateNoiseFloor(double powerRatio)
|
||||
{
|
||||
boolean voice = mVoiceModulationDetector.analyze();
|
||||
mCarrierDetected = powerRatio < NOISE_POWER_RATIO;
|
||||
|
||||
if(mCarrierDetected)
|
||||
{
|
||||
updateCarrier(voice);
|
||||
}
|
||||
else
|
||||
{
|
||||
resetCarrierTracking();
|
||||
mCarrierTimedOut = false;
|
||||
mNoiseFloorBeforeCarrier = null;
|
||||
addNoisePower(mPower);
|
||||
}
|
||||
|
||||
if(mNoiseHistoryCount > 0)
|
||||
{
|
||||
updateTrackedThreshold();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the carrier timeout from an interval that doesn't look like noise. A carrier that lasts for the timeout
|
||||
* period is treated as the noise floor, unless it has voice modulation.
|
||||
* @param voice true if voice modulation was detected in this interval
|
||||
*/
|
||||
private void updateCarrier(boolean voice)
|
||||
{
|
||||
mCarrierIntervalCount++;
|
||||
|
||||
if(voice)
|
||||
{
|
||||
mVoiceIntervalCount++;
|
||||
}
|
||||
|
||||
boolean hasVoice = mVoiceIntervalCount >= MINIMUM_VOICE_INTERVALS;
|
||||
|
||||
if(mCarrierTimedOut && hasVoice)
|
||||
{
|
||||
restoreNoiseFloor();
|
||||
resetCarrierTracking();
|
||||
}
|
||||
else if(mCarrierTimedOut)
|
||||
{
|
||||
addNoisePower(mPower);
|
||||
}
|
||||
else if(mCarrierIntervalCount >= CARRIER_TIMEOUT_INTERVALS && !hasVoice)
|
||||
{
|
||||
//Replace the history so that the carrier takes effect immediately.
|
||||
mCarrierTimedOut = true;
|
||||
mNoiseFloorBeforeCarrier = mNoiseFloor;
|
||||
mNoiseHistoryCount = 0;
|
||||
addNoisePower(mPower);
|
||||
}
|
||||
|
||||
if(mCarrierIntervalCount >= CARRIER_TIMEOUT_INTERVALS)
|
||||
{
|
||||
resetCarrierTracking();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the counts for the current carrier evaluation period.
|
||||
*/
|
||||
private void resetCarrierTracking()
|
||||
{
|
||||
mCarrierIntervalCount = 0;
|
||||
mVoiceIntervalCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a timed out carrier from the noise floor, restoring the noise floor from before the carrier. If there's
|
||||
* no noise floor from before the carrier, the user threshold is restored until the noise floor can be measured.
|
||||
*/
|
||||
private void restoreNoiseFloor()
|
||||
{
|
||||
mCarrierTimedOut = false;
|
||||
mNoiseHistoryCount = 0;
|
||||
|
||||
if(mNoiseFloorBeforeCarrier != null)
|
||||
{
|
||||
addNoisePower(mNoiseFloorBeforeCarrier);
|
||||
mNoiseFloorBeforeCarrier = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
mNoiseFloor = null;
|
||||
mLowestNoiseFloor = null;
|
||||
applySquelchThreshold(mUserThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the power to the noise history.
|
||||
* @param power of the interval
|
||||
*/
|
||||
private void addNoisePower(float power)
|
||||
{
|
||||
mNoiseHistory[mNoiseHistoryPointer] = power;
|
||||
mNoiseHistoryPointer = (mNoiseHistoryPointer + 1) % NOISE_WINDOW_INTERVALS;
|
||||
mNoiseHistoryCount = Math.min(mNoiseHistoryCount + 1, NOISE_WINDOW_INTERVALS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the squelch threshold to the margin over the current noise floor, where the margin is the larger of the
|
||||
* minimum margin and the amount that the user threshold exceeds the lowest observed noise floor.
|
||||
*/
|
||||
private void updateTrackedThreshold()
|
||||
{
|
||||
float noiseFloor = Float.MAX_VALUE;
|
||||
|
||||
for(int x = 0; x < mNoiseHistoryCount; x++)
|
||||
{
|
||||
int index = (mNoiseHistoryPointer - 1 - x + NOISE_WINDOW_INTERVALS) % NOISE_WINDOW_INTERVALS;
|
||||
noiseFloor = Math.min(noiseFloor, mNoiseHistory[index]);
|
||||
}
|
||||
|
||||
mNoiseFloor = noiseFloor;
|
||||
mLowestNoiseFloor = (mLowestNoiseFloor == null) ? noiseFloor : Math.min(mLowestNoiseFloor, noiseFloor);
|
||||
float threshold = noiseFloor * Math.max(MINIMUM_SQUELCH_MARGIN, mUserThreshold / mLowestNoiseFloor);
|
||||
|
||||
if(threshold != mSquelchThreshold)
|
||||
{
|
||||
applySquelchThreshold(threshold);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,6 +471,15 @@ public class AdaptiveSquelch implements Listener<SourceEvent>
|
||||
return !mSquelch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if audio should be passed. This is false during the hang period, while the signal is below the close
|
||||
* threshold but the squelch is still open.
|
||||
*/
|
||||
public boolean isAudioEnabled()
|
||||
{
|
||||
return !mSquelch && mHangCount == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current power level
|
||||
* @return current power level in dB
|
||||
@@ -309,6 +534,7 @@ public class AdaptiveSquelch implements Listener<SourceEvent>
|
||||
{
|
||||
case REQUEST_CHANGE_SQUELCH_THRESHOLD:
|
||||
setSquelchThreshold(sourceEvent.getValue().floatValue());
|
||||
broadcast(SourceEvent.squelchUserThreshold(toDb(mUserThreshold)));
|
||||
break;
|
||||
case REQUEST_CURRENT_SQUELCH_THRESHOLD:
|
||||
broadcast(SourceEvent.squelchThreshold(null, getSquelchThreshold()));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2025 Dennis Sheirer
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -23,9 +23,11 @@ import io.github.dsheirer.audio.squelch.SquelchState;
|
||||
import io.github.dsheirer.dsp.filter.FilterFactory;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.IRealFilter;
|
||||
import io.github.dsheirer.dsp.filter.fir.real.RealFIRFilter;
|
||||
import io.github.dsheirer.dsp.fm.ScalarFMDemodulator;
|
||||
import io.github.dsheirer.dsp.window.WindowType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Squelch processor for unfiltered, demodulated audio that operators by high-pass filtering the audio to remove below
|
||||
@@ -47,6 +49,12 @@ public class NoiseSquelch implements INoiseSquelchController
|
||||
public static final float DEFAULT_NOISE_CLOSE_THRESHOLD = 0.19f;
|
||||
public static final float MINIMUM_NOISE_THRESHOLD = 0.1f;
|
||||
public static final float MAXIMUM_NOISE_THRESHOLD = 0.5f;
|
||||
private static final int HIGH_PASS_FILTER_CUTOFF = 3000;
|
||||
private static final int HIGH_PASS_FILTER_LENGTH = 31;
|
||||
//Noise-only variance of a 12.5 kHz channel at 25 kHz, which is the reference that the noise thresholds are set against.
|
||||
private static final float REFERENCE_NOISE_VARIANCE = 0.298f;
|
||||
private static final int NOISE_MEASUREMENT_SETTLING_SAMPLES = 512;
|
||||
private static final long NOISE_MEASUREMENT_SEED = 1L;
|
||||
private float[] mFilteredBuffer = new float[0];
|
||||
private float[] mAudioBuffer = new float[0];
|
||||
private float mMeanAccumulator;
|
||||
@@ -227,7 +235,7 @@ public class NoiseSquelch implements INoiseSquelchController
|
||||
*/
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
float[] coefficients = FilterFactory.getHighPass((int)sampleRate, 3000, 31, WindowType.BLACKMAN_HARRIS_7);
|
||||
float[] coefficients = getHighPassCoefficients(sampleRate);
|
||||
mHighPassFilter = new RealFIRFilter(coefficients);
|
||||
mVarianceWindowSize = (int)(sampleRate * (VARIANCE_CALCULATION_WINDOW_MILLISECONDS / 1000.0));
|
||||
|
||||
@@ -235,6 +243,88 @@ public class NoiseSquelch implements INoiseSquelchController
|
||||
mAudioBufferFilterDelay = (int)Math.ceil(coefficients.length / 2.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sample rate for the incoming demodulated audio sample stream and normalizes the noise measurement for
|
||||
* the channel filter applied ahead of the FM demodulator. The demodulated noise variance depends on the ratio of
|
||||
* the channel bandwidth to the sample rate and, without normalization, a narrow channel can have a noise-only
|
||||
* variance that is below the noise close threshold, which holds the squelch open. The noise measurement is scaled
|
||||
* up so that a noise-only channel measures at the reference noise variance. The noise measurement is never scaled
|
||||
* down, so channels that are already above the reference are unchanged.
|
||||
*
|
||||
* @param sampleRate of the sample stream.
|
||||
* @param channelFilter coefficients of the low-pass channel filter applied ahead of the FM demodulator.
|
||||
*/
|
||||
public void setSampleRate(double sampleRate, float[] channelFilter)
|
||||
{
|
||||
setSampleRate(sampleRate);
|
||||
|
||||
float gain = (float)Math.sqrt(Math.max(1.0f, REFERENCE_NOISE_VARIANCE / getNoiseVariance(sampleRate, channelFilter)));
|
||||
float[] coefficients = getHighPassCoefficients(sampleRate);
|
||||
|
||||
for(int x = 0; x < coefficients.length; x++)
|
||||
{
|
||||
coefficients[x] *= gain;
|
||||
}
|
||||
|
||||
mHighPassFilter = new RealFIRFilter(coefficients);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the high-pass filter that isolates the noise in the demodulated audio.
|
||||
* @param sampleRate of the demodulated audio.
|
||||
* @return filter coefficients
|
||||
*/
|
||||
private static float[] getHighPassCoefficients(double sampleRate)
|
||||
{
|
||||
return FilterFactory.getHighPass((int)sampleRate, HIGH_PASS_FILTER_CUTOFF, HIGH_PASS_FILTER_LENGTH,
|
||||
WindowType.BLACKMAN_HARRIS_7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Measures the variance that the squelch sees when the channel contains only noise, using one second of a
|
||||
* repeatable sequence of complex noise samples.
|
||||
*
|
||||
* @param sampleRate of the channel.
|
||||
* @param channelFilter coefficients of the low-pass channel filter applied ahead of the FM demodulator.
|
||||
* @return noise-only variance of the demodulated, high-pass filtered samples.
|
||||
*/
|
||||
public static float getNoiseVariance(double sampleRate, float[] channelFilter)
|
||||
{
|
||||
int length = (int)sampleRate;
|
||||
float[] i = new float[length];
|
||||
float[] q = new float[length];
|
||||
Random random = new Random(NOISE_MEASUREMENT_SEED);
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
i[x] = (float)random.nextGaussian();
|
||||
q[x] = (float)random.nextGaussian();
|
||||
}
|
||||
|
||||
float[] demodulated = new ScalarFMDemodulator().demodulate(new RealFIRFilter(channelFilter.clone()).filter(i),
|
||||
new RealFIRFilter(channelFilter.clone()).filter(q));
|
||||
float[] noise = new RealFIRFilter(getHighPassCoefficients(sampleRate)).filter(demodulated);
|
||||
|
||||
int count = length - NOISE_MEASUREMENT_SETTLING_SAMPLES;
|
||||
double mean = 0.0;
|
||||
|
||||
for(int x = NOISE_MEASUREMENT_SETTLING_SAMPLES; x < length; x++)
|
||||
{
|
||||
mean += noise[x];
|
||||
}
|
||||
|
||||
mean /= count;
|
||||
|
||||
double variance = 0.0;
|
||||
|
||||
for(int x = NOISE_MEASUREMENT_SETTLING_SAMPLES; x < length; x++)
|
||||
{
|
||||
variance += (noise[x] - mean) * (noise[x] - mean);
|
||||
}
|
||||
|
||||
return (float)(variance / count);
|
||||
}
|
||||
|
||||
public void process(float[] samples)
|
||||
{
|
||||
float[] filtered = mHighPassFilter.filter(samples);
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.dsp.squelch;
|
||||
|
||||
import org.jtransforms.fft.FloatFFT_1D;
|
||||
|
||||
/**
|
||||
* Detects voice modulation on the envelope of a steady AM carrier. The noise on a carrier's envelope is spectrally flat
|
||||
* across the channel, while voice energy is concentrated in the lower part of the voice band. The detector compares
|
||||
* the average spectral density of a low and a high band, with band edges limited to the channel pass band.
|
||||
*
|
||||
* A single detection isn't proof of a voice transmission and a pause in speech isn't proof of a dead carrier, so the
|
||||
* results should be evaluated across a longer period.
|
||||
*/
|
||||
public class VoiceModulationDetector
|
||||
{
|
||||
private static final float MAXIMUM_FREQUENCY = 2300.0f;
|
||||
private static final float MAXIMUM_FREQUENCY_BANDWIDTH_RATIO = 0.38f;
|
||||
private static final float LOW_BAND_START = 300.0f;
|
||||
private static final float LOW_BAND_STOP_RATIO = 0.52f;
|
||||
private static final float HIGH_BAND_START_RATIO = 0.65f;
|
||||
private static final double DETECTION_THRESHOLD_DB = 5.0;
|
||||
|
||||
private final double mBandwidth;
|
||||
private FloatFFT_1D mFFT;
|
||||
private float[] mEnvelope;
|
||||
private float[] mWindow;
|
||||
private int mLowStartBin;
|
||||
private int mLowStopBin;
|
||||
private int mHighStartBin;
|
||||
private int mHighStopBin;
|
||||
private int mPointer = 0;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param sampleRate of the envelope samples in hertz
|
||||
* @param bandwidth of the channel in hertz, spanning positive and negative frequencies
|
||||
* @param analysisSamples maximum number of samples per analysis. This is reduced to a power of 2.
|
||||
*/
|
||||
public VoiceModulationDetector(double sampleRate, double bandwidth, int analysisSamples)
|
||||
{
|
||||
mBandwidth = bandwidth;
|
||||
configure(sampleRate, analysisSamples);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the sample rate and resets this detector.
|
||||
* @param sampleRate of the envelope samples in hertz
|
||||
* @param analysisSamples maximum number of samples per analysis. This is reduced to a power of 2.
|
||||
*/
|
||||
public final void configure(double sampleRate, int analysisSamples)
|
||||
{
|
||||
int length = Integer.highestOneBit(analysisSamples);
|
||||
mFFT = new FloatFFT_1D(length);
|
||||
mEnvelope = new float[length];
|
||||
mWindow = new float[length];
|
||||
|
||||
for(int x = 0; x < length; x++)
|
||||
{
|
||||
mWindow[x] = (float)(0.5 - 0.5 * Math.cos(2.0 * Math.PI * x / length));
|
||||
}
|
||||
|
||||
float stop = Math.min(MAXIMUM_FREQUENCY, (float)mBandwidth * MAXIMUM_FREQUENCY_BANDWIDTH_RATIO);
|
||||
double hertzPerBin = sampleRate / length;
|
||||
mLowStartBin = (int)Math.ceil(LOW_BAND_START / hertzPerBin);
|
||||
mLowStopBin = (int)(stop * LOW_BAND_STOP_RATIO / hertzPerBin);
|
||||
mHighStartBin = (int)Math.ceil(stop * HIGH_BAND_START_RATIO / hertzPerBin);
|
||||
mHighStopBin = (int)(stop / hertzPerBin);
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards any partial analysis buffer.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mPointer = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an envelope sample to the analysis buffer. Samples are ignored once the buffer is full.
|
||||
* @param envelope sample
|
||||
*/
|
||||
public void receive(float envelope)
|
||||
{
|
||||
if(mPointer < mEnvelope.length)
|
||||
{
|
||||
mEnvelope[mPointer++] = envelope;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the buffered samples and resets this detector.
|
||||
* @return true if the analysis buffer is full and the samples contain voice modulation.
|
||||
*/
|
||||
public boolean analyze()
|
||||
{
|
||||
boolean full = mPointer == mEnvelope.length;
|
||||
mPointer = 0;
|
||||
|
||||
if(!full)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float mean = 0.0f;
|
||||
|
||||
for(float sample: mEnvelope)
|
||||
{
|
||||
mean += sample;
|
||||
}
|
||||
|
||||
mean /= mEnvelope.length;
|
||||
|
||||
for(int x = 0; x < mEnvelope.length; x++)
|
||||
{
|
||||
mEnvelope[x] = (mEnvelope[x] - mean) * mWindow[x];
|
||||
}
|
||||
|
||||
mFFT.realForward(mEnvelope);
|
||||
double low = getBandPower(mLowStartBin, mLowStopBin);
|
||||
double high = getBandPower(mHighStartBin, mHighStopBin);
|
||||
return high > 0.0 && 10.0 * Math.log10(low / high) >= DETECTION_THRESHOLD_DB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the mean power of the bins from the real transform output of interleaved real and imaginary values.
|
||||
*/
|
||||
private double getBandPower(int startBin, int stopBin)
|
||||
{
|
||||
double power = 0.0;
|
||||
|
||||
for(int bin = startBin; bin < stopBin; bin++)
|
||||
{
|
||||
double real = mEnvelope[2 * bin];
|
||||
double imaginary = mEnvelope[2 * bin + 1];
|
||||
power += real * real + imaginary * imaginary;
|
||||
}
|
||||
|
||||
return power / Math.max(1, stopBin - startBin);
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,6 @@ import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.TunerEvent;
|
||||
import io.github.dsheirer.source.tuner.manager.DiscoveredTuner;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.source.tuner.sdrplay.api.SDRPlayLibraryHelper;
|
||||
import io.github.dsheirer.source.tuner.ui.TunerSpectralDisplayManager;
|
||||
import io.github.dsheirer.spectrum.DisableSpectrumWaterfallMenuItem;
|
||||
import io.github.dsheirer.spectrum.ShowTunerMenuItem;
|
||||
@@ -154,14 +153,6 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
mApplicationLog = new ApplicationLog(mUserPreferences);
|
||||
mApplicationLog.start();
|
||||
|
||||
//Note: invoke this early in the application lifecycle, before the TunerManager causes the sdrplay classes
|
||||
//to be loaded since the jextract auto-generated code attempts to load the library by name and that can fail
|
||||
//when the library was not installed into a normal/default location, particularly on windows OS systems.
|
||||
if(SDRPlayLibraryHelper.LOADED)
|
||||
{
|
||||
mLog.info("SDRPlay API native library preemptively loaded");
|
||||
}
|
||||
|
||||
mResourceMonitor = new ResourceMonitor(mUserPreferences);
|
||||
|
||||
ThreadPool.logSettings();
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.control;
|
||||
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import org.controlsfx.control.SegmentedButton;
|
||||
|
||||
/**
|
||||
* Screen reader (accessibility) utilities for JavaFX controls.
|
||||
*/
|
||||
public final class AccessibilityUtil
|
||||
{
|
||||
private AccessibilityUtil()
|
||||
{
|
||||
//Not instantiable
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an accessible name for each toggle button in a segmented button group, combining the group label with
|
||||
* the button text (e.g. "Channel Bandwidth: 12.5 kHz").
|
||||
*
|
||||
* A segmented button is a container, so a label can't identify it via Label.setLabelFor() in a way that screen
|
||||
* readers announce: each toggle button receives focus individually and would otherwise be announced by its own text
|
||||
* only, without the context of the group. Buttons added to the group later are also named.
|
||||
*
|
||||
* @param groupLabel that visually identifies the segmented button group
|
||||
* @param segmentedButton containing the toggle buttons
|
||||
*/
|
||||
public static void labelGroup(Label groupLabel, SegmentedButton segmentedButton)
|
||||
{
|
||||
String groupName = groupLabel.getText() == null ? "" : groupLabel.getText().trim();
|
||||
|
||||
//Remove a trailing colon so that the name doesn't announce "colon colon"
|
||||
if(groupName.endsWith(":"))
|
||||
{
|
||||
groupName = groupName.substring(0, groupName.length() - 1).trim();
|
||||
}
|
||||
|
||||
final String name = groupName;
|
||||
|
||||
for(ToggleButton button: segmentedButton.getButtons())
|
||||
{
|
||||
setGroupName(name, button);
|
||||
}
|
||||
|
||||
segmentedButton.getButtons().addListener((ListChangeListener<ToggleButton>)change -> {
|
||||
while(change.next())
|
||||
{
|
||||
for(ToggleButton added: change.getAddedSubList())
|
||||
{
|
||||
setGroupName(name, added);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the accessible text for the button as the group name and button text.
|
||||
*/
|
||||
private static void setGroupName(String groupName, ToggleButton button)
|
||||
{
|
||||
String text = button.getText() == null ? "" : button.getText();
|
||||
button.setAccessibleText(groupName.isEmpty() ? text : groupName + ": " + text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2026 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.gui.control;
|
||||
|
||||
import javafx.scene.AccessibleAction;
|
||||
import javafx.scene.AccessibleAttribute;
|
||||
import javafx.scene.AccessibleRole;
|
||||
import org.controlsfx.control.ToggleSwitch;
|
||||
|
||||
/**
|
||||
* ControlsFX toggle switch that exposes its role and on/off state to screen readers.
|
||||
*
|
||||
* The ControlsFX toggle switch doesn't provide an accessible role or selected state, so assistive technology sees it
|
||||
* as a generic node with no state. This version reports itself as a toggle button, reports the selected state, and
|
||||
* supports the accessible fire (toggle) action. The accessible name comes from the switch text, or from a label that
|
||||
* identifies this switch via Label.setLabelFor().
|
||||
*/
|
||||
public class AccessibleToggleSwitch extends ToggleSwitch
|
||||
{
|
||||
/**
|
||||
* Constructs an instance with no text. Use Label.setLabelFor() or setAccessibleText() to provide an accessible name.
|
||||
*/
|
||||
public AccessibleToggleSwitch()
|
||||
{
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param text to display alongside the switch, also used as the accessible name.
|
||||
*/
|
||||
public AccessibleToggleSwitch(String text)
|
||||
{
|
||||
super(text);
|
||||
setAccessibleRole(AccessibleRole.TOGGLE_BUTTON);
|
||||
selectedProperty().addListener((observable, oldValue, newValue) ->
|
||||
notifyAccessibleAttributeChanged(AccessibleAttribute.SELECTED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters)
|
||||
{
|
||||
if(attribute == AccessibleAttribute.SELECTED)
|
||||
{
|
||||
return isSelected();
|
||||
}
|
||||
|
||||
return super.queryAccessibleAttribute(attribute, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeAccessibleAction(AccessibleAction action, Object... parameters)
|
||||
{
|
||||
if(action == AccessibleAction.FIRE)
|
||||
{
|
||||
fire();
|
||||
}
|
||||
else
|
||||
{
|
||||
super.executeAccessibleAction(action, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.UIManager;
|
||||
|
||||
/**
|
||||
@@ -575,7 +576,17 @@ public class JFrequencyControl extends JPanel implements ISourceEventProcessor
|
||||
@Override
|
||||
public void mouseWheelMoved(MouseWheelEvent e)
|
||||
{
|
||||
set(mValue - e.getWheelRotation());
|
||||
//Swing delivers mouse wheel events to disabled components, so check enabled state explicitly so that
|
||||
//the wheel can't bypass the lock when the tuner is providing channels.
|
||||
if(isEnabled())
|
||||
{
|
||||
set(mValue - e.getWheelRotation());
|
||||
}
|
||||
else if(getParent() != null)
|
||||
{
|
||||
//Pass the event up so that an enclosing scroll pane can still scroll while this control is locked
|
||||
getParent().dispatchEvent(SwingUtilities.convertMouseEvent(Digit.this, e, getParent()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ package io.github.dsheirer.gui.playlist.alias;
|
||||
|
||||
import com.google.common.collect.Ordering;
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.gui.control.AccessibleToggleSwitch;
|
||||
import io.github.dsheirer.gui.playlist.Editor;
|
||||
import io.github.dsheirer.icon.Icon;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
@@ -100,6 +101,7 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
gridPane.getChildren().add(separator);
|
||||
|
||||
Label colorLabel = new Label("Color");
|
||||
colorLabel.setLabelFor(getColorPicker());
|
||||
GridPane.setHalignment(colorLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(colorLabel, 0, ++row);
|
||||
gridPane.getChildren().add(colorLabel);
|
||||
@@ -114,6 +116,7 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
gridPane.getChildren().add(getResetColorButton());
|
||||
|
||||
Label iconLabel = new Label("Icon");
|
||||
iconLabel.setLabelFor(getIconNodeComboBox());
|
||||
GridPane.setHalignment(iconLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(iconLabel, 0, ++row);
|
||||
gridPane.getChildren().add(iconLabel);
|
||||
@@ -125,6 +128,7 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
gridPane.getChildren().add(getApplyIconButton());
|
||||
|
||||
Label listenLabel = new Label("Listen");
|
||||
listenLabel.setLabelFor(getMonitorAudioToggleSwitch());
|
||||
GridPane.setHalignment(listenLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(listenLabel, 0, ++row);
|
||||
gridPane.getChildren().add(listenLabel);
|
||||
@@ -133,6 +137,7 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
gridPane.getChildren().add(getMonitorAudioToggleSwitch());
|
||||
|
||||
Label priorityLabel = new Label("Priority");
|
||||
priorityLabel.setLabelFor(getMonitorPriorityComboBox());
|
||||
GridPane.setHalignment(priorityLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(priorityLabel, 2, row);
|
||||
gridPane.getChildren().add(priorityLabel);
|
||||
@@ -144,6 +149,7 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
gridPane.getChildren().add(getApplyMonitorButton());
|
||||
|
||||
Label recordLabel = new Label("Record");
|
||||
recordLabel.setLabelFor(getRecordToggleSwitch());
|
||||
GridPane.setHalignment(recordLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(recordLabel, 0, ++row);
|
||||
gridPane.getChildren().add(recordLabel);
|
||||
@@ -235,6 +241,7 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
if(mApplyColorButton == null)
|
||||
{
|
||||
mApplyColorButton = new Button("Apply");
|
||||
mApplyColorButton.setAccessibleText("Apply color to selected aliases");
|
||||
mApplyColorButton.setOnAction(event ->
|
||||
{
|
||||
startChange();
|
||||
@@ -258,6 +265,7 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
if(mResetColorButton == null)
|
||||
{
|
||||
mResetColorButton = new Button("Reset Color");
|
||||
mResetColorButton.setAccessibleText("Reset color for selected aliases");
|
||||
mResetColorButton.setOnAction(event ->
|
||||
{
|
||||
startChange();
|
||||
@@ -297,6 +305,7 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
if(mApplyIconButton == null)
|
||||
{
|
||||
mApplyIconButton = new Button("Apply");
|
||||
mApplyIconButton.setAccessibleText("Apply icon to selected aliases");
|
||||
mApplyIconButton.setDisable(true);
|
||||
mApplyIconButton.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@@ -327,7 +336,7 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
{
|
||||
if(mMonitorAudioToggleSwitch == null)
|
||||
{
|
||||
mMonitorAudioToggleSwitch = new ToggleSwitch();
|
||||
mMonitorAudioToggleSwitch = new AccessibleToggleSwitch();
|
||||
mMonitorAudioToggleSwitch.setSelected(true);
|
||||
mMonitorAudioToggleSwitch.selectedProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
@@ -340,7 +349,7 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
{
|
||||
if(mRecordToggleSwitch == null)
|
||||
{
|
||||
mRecordToggleSwitch = new ToggleSwitch();
|
||||
mRecordToggleSwitch = new AccessibleToggleSwitch();
|
||||
mRecordToggleSwitch.setSelected(false);
|
||||
mRecordToggleSwitch.selectedProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
@@ -374,6 +383,7 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
if(mApplyMonitorButton == null)
|
||||
{
|
||||
mApplyMonitorButton = new Button("Apply");
|
||||
mApplyMonitorButton.setAccessibleText("Apply listen and priority to selected aliases");
|
||||
mApplyMonitorButton.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
@@ -414,6 +424,7 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
if(mApplyRecordButton == null)
|
||||
{
|
||||
mApplyRecordButton = new Button("Apply");
|
||||
mApplyRecordButton.setAccessibleText("Apply record to selected aliases");
|
||||
mApplyRecordButton.setOnAction(event -> {
|
||||
startChange();
|
||||
boolean record = getRecordToggleSwitch().isSelected();
|
||||
@@ -456,12 +467,15 @@ public class AliasBulkEditor extends Editor<List<Alias>>
|
||||
{
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
setAccessibleText(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
textLabel.setText(item.getName());
|
||||
iconLabel.setGraphic(new ImageView(item.getFxImage()));
|
||||
setGraphic(gridPane);
|
||||
//The name is shown in a nested label, so expose it as the cell's accessible text
|
||||
setAccessibleText(item.getName());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -273,7 +273,9 @@ public class AliasConfigurationEditor extends SplitPane implements IAliasListRef
|
||||
|
||||
|
||||
Label listLabel = new Label("Alias List");
|
||||
listLabel.setLabelFor(getAliasListNameComboBox());
|
||||
Label searchLabel = new Label("Search");
|
||||
searchLabel.setLabelFor(getSearchField());
|
||||
searchLabel.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
HBox searchBox = new HBox();
|
||||
@@ -451,6 +453,7 @@ public class AliasConfigurationEditor extends SplitPane implements IAliasListRef
|
||||
if(mAliasTableView == null)
|
||||
{
|
||||
mAliasTableView = new TableView<>();
|
||||
mAliasTableView.setAccessibleText("Aliases");
|
||||
|
||||
TableColumn nameColumn = new TableColumn();
|
||||
nameColumn.setText("Alias");
|
||||
@@ -751,15 +754,18 @@ public class AliasConfigurationEditor extends SplitPane implements IAliasListRef
|
||||
UIManager.getColor("TextField.background"));
|
||||
rectangle.setFill(ColorUtil.fromInteger(readable.equals(color) ? alias.getColor() :
|
||||
readable.getRGB()));
|
||||
setAccessibleText(String.format("#%06X", alias.getColor() & 0xFFFFFF));
|
||||
}
|
||||
else
|
||||
{
|
||||
rectangle.setVisible(false);
|
||||
setAccessibleText(null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rectangle.setVisible(false);
|
||||
setAccessibleText(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -798,13 +804,15 @@ public class AliasConfigurationEditor extends SplitPane implements IAliasListRef
|
||||
{
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if(!empty && getTableRow() != null)
|
||||
if(!empty && getTableRow() != null && item != null)
|
||||
{
|
||||
iconNode.setVisible(item);
|
||||
setAccessibleText(item ? "Yes" : "No");
|
||||
}
|
||||
else
|
||||
{
|
||||
iconNode.setVisible(false);
|
||||
setAccessibleText(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -919,6 +927,7 @@ public class AliasConfigurationEditor extends SplitPane implements IAliasListRef
|
||||
{
|
||||
super.updateItem(item, empty);
|
||||
setAlignment(Pos.CENTER);
|
||||
setAccessibleText(null);
|
||||
|
||||
if(empty)
|
||||
{
|
||||
@@ -937,6 +946,7 @@ public class AliasConfigurationEditor extends SplitPane implements IAliasListRef
|
||||
if(icon != null && icon.getFxImage() != null)
|
||||
{
|
||||
setGraphic(new ImageView(icon.getFxImage()));
|
||||
setAccessibleText(icon.getName());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -57,6 +57,7 @@ import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||
import io.github.dsheirer.alias.id.tone.TonesID;
|
||||
import io.github.dsheirer.audio.broadcast.ConfiguredBroadcast;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.gui.control.AccessibleToggleSwitch;
|
||||
import io.github.dsheirer.gui.control.IntegerFormatter;
|
||||
import io.github.dsheirer.gui.playlist.Editor;
|
||||
import io.github.dsheirer.gui.playlist.alias.action.ActionEditor;
|
||||
@@ -574,6 +575,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
{
|
||||
mActionsList = new ListView<>(FXCollections.observableArrayList(AliasAction.extractor()));
|
||||
mActionsList.setPrefHeight(75);
|
||||
mActionsList.setAccessibleText("Alias actions");
|
||||
mActionsList.setDisable(true);
|
||||
mActionsList.getSelectionModel().selectedItemProperty()
|
||||
.addListener((observable, oldValue, newValue) -> setAction(newValue));
|
||||
@@ -686,6 +688,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
mIdentifiersList = new ListView<>(FXCollections.observableArrayList(AliasID.extractor()));
|
||||
mIdentifiersList.setDisable(true);
|
||||
mIdentifiersList.setPrefHeight(75);
|
||||
mIdentifiersList.setAccessibleText("Alias identifiers");
|
||||
mIdentifiersList.getSelectionModel().selectedItemProperty()
|
||||
.addListener((observable, oldValue, newValue) -> setIdentifier(newValue));
|
||||
mIdentifiersList.setCellFactory(param -> new AliasIdentifierCell());
|
||||
@@ -846,13 +849,17 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
buttonBox.setSpacing(5);
|
||||
buttonBox.getChildren().addAll(new Label(" "), getAddStreamButton(), getRemoveStreamButton());
|
||||
|
||||
Label availableLabel = new Label("Available");
|
||||
getAvailableStreamsView().setAccessibleText("Available streams");
|
||||
VBox availableBox = new VBox();
|
||||
VBox.setVgrow(getAvailableStreamsView(), Priority.ALWAYS);
|
||||
availableBox.getChildren().addAll(new Label("Available"), getAvailableStreamsView());
|
||||
availableBox.getChildren().addAll(availableLabel, getAvailableStreamsView());
|
||||
|
||||
Label selectedLabel = new Label("Selected");
|
||||
getSelectedStreamsView().setAccessibleText("Selected streams");
|
||||
VBox selectedBox = new VBox();
|
||||
VBox.setVgrow(getSelectedStreamsView(), Priority.ALWAYS);
|
||||
selectedBox.getChildren().addAll(new Label("Selected"), getSelectedStreamsView());
|
||||
selectedBox.getChildren().addAll(selectedLabel, getSelectedStreamsView());
|
||||
|
||||
HBox hbox = new HBox();
|
||||
hbox.setSpacing(10);
|
||||
@@ -864,6 +871,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
vbox.setSpacing(10);
|
||||
VBox.setVgrow(hbox, Priority.ALWAYS);
|
||||
Label label = new Label("Stream this Alias as Talkgroup:");
|
||||
label.setLabelFor(getStreamAsTalkgroupField());
|
||||
HBox streamAsHBox = new HBox();
|
||||
streamAsHBox.setAlignment(Pos.CENTER_LEFT);
|
||||
streamAsHBox.setSpacing(10);
|
||||
@@ -964,6 +972,8 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
.or(Bindings.isNull(getAvailableStreamsView().getSelectionModel().selectedItemProperty())));
|
||||
mAddStreamButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mAddStreamButton.setGraphic(new IconNode(FontAwesome.ANGLE_RIGHT));
|
||||
mAddStreamButton.setAccessibleText("Add selected stream");
|
||||
mAddStreamButton.setTooltip(new Tooltip("Add selected stream"));
|
||||
mAddStreamButton.setAlignment(Pos.CENTER);
|
||||
mAddStreamButton.setOnAction(event -> {
|
||||
String stream = getAvailableStreamsView().getSelectionModel().getSelectedItem();
|
||||
@@ -990,6 +1000,8 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
.or(Bindings.isNull(getSelectedStreamsView().getSelectionModel().selectedItemProperty())));
|
||||
mRemoveStreamButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mRemoveStreamButton.setGraphic(new IconNode(FontAwesome.ANGLE_LEFT));
|
||||
mRemoveStreamButton.setAccessibleText("Remove selected stream");
|
||||
mRemoveStreamButton.setTooltip(new Tooltip("Remove selected stream"));
|
||||
mRemoveStreamButton.setAlignment(Pos.CENTER);
|
||||
mRemoveStreamButton.setOnAction(event -> {
|
||||
BroadcastChannel broadcastChannel = getSelectedStreamsView().getSelectionModel().getSelectedItem();
|
||||
@@ -1050,6 +1062,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
int row = 0;
|
||||
|
||||
Label nameLabel = new Label("Alias");
|
||||
nameLabel.setLabelFor(getNameField());
|
||||
GridPane.setHalignment(nameLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(nameLabel, 0, row);
|
||||
mTextFieldPane.getChildren().add(nameLabel);
|
||||
@@ -1058,6 +1071,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
mTextFieldPane.getChildren().add(getNameField());
|
||||
|
||||
Label monitorAudioLabel = new Label("Listen");
|
||||
monitorAudioLabel.setLabelFor(getMonitorAudioToggleSwitch());
|
||||
GridPane.setHalignment(monitorAudioLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(monitorAudioLabel, 2, row);
|
||||
mTextFieldPane.getChildren().add(monitorAudioLabel);
|
||||
@@ -1065,6 +1079,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
mTextFieldPane.getChildren().add(getMonitorAudioToggleSwitch());
|
||||
|
||||
Label monitorPriorityLabel = new Label("Priority");
|
||||
monitorPriorityLabel.setLabelFor(getMonitorPriorityComboBox());
|
||||
GridPane.setHalignment(monitorPriorityLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(monitorPriorityLabel, 4, row);
|
||||
mTextFieldPane.getChildren().add(monitorPriorityLabel);
|
||||
@@ -1072,6 +1087,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
mTextFieldPane.getChildren().add(getMonitorPriorityComboBox());
|
||||
|
||||
Label colorLabel = new Label("Color");
|
||||
colorLabel.setLabelFor(getColorPicker());
|
||||
GridPane.setHalignment(colorLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(colorLabel, 6, row);
|
||||
mTextFieldPane.getChildren().add(colorLabel);
|
||||
@@ -1079,6 +1095,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
mTextFieldPane.getChildren().add(getColorPicker());
|
||||
|
||||
Label groupLabel = new Label("Group");
|
||||
groupLabel.setLabelFor(getGroupField());
|
||||
GridPane.setHalignment(groupLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(groupLabel, 0, ++row);
|
||||
mTextFieldPane.getChildren().add(groupLabel);
|
||||
@@ -1087,6 +1104,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
mTextFieldPane.getChildren().add(getGroupField());
|
||||
|
||||
Label recordAudioLabel = new Label("Record");
|
||||
recordAudioLabel.setLabelFor(getRecordAudioToggleSwitch());
|
||||
GridPane.setHalignment(recordAudioLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(recordAudioLabel, 2, row);
|
||||
mTextFieldPane.getChildren().add(recordAudioLabel);
|
||||
@@ -1094,6 +1112,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
mTextFieldPane.getChildren().add(getRecordAudioToggleSwitch());
|
||||
|
||||
Label iconLabel = new Label("Icon");
|
||||
iconLabel.setLabelFor(getIconNodeComboBox());
|
||||
GridPane.setHalignment(iconLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(iconLabel, 4, row);
|
||||
mTextFieldPane.getChildren().add(iconLabel);
|
||||
@@ -1108,7 +1127,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
{
|
||||
if(mMonitorAudioToggleSwitch == null)
|
||||
{
|
||||
mMonitorAudioToggleSwitch = new ToggleSwitch();
|
||||
mMonitorAudioToggleSwitch = new AccessibleToggleSwitch();
|
||||
mMonitorAudioToggleSwitch.setDisable(true);
|
||||
mMonitorAudioToggleSwitch.selectedProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
@@ -1141,7 +1160,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
{
|
||||
if(mRecordAudioToggleSwitch == null)
|
||||
{
|
||||
mRecordAudioToggleSwitch = new ToggleSwitch();
|
||||
mRecordAudioToggleSwitch = new AccessibleToggleSwitch();
|
||||
mRecordAudioToggleSwitch.setDisable(true);
|
||||
mRecordAudioToggleSwitch.selectedProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
@@ -1820,12 +1839,15 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
{
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
setAccessibleText(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
textLabel.setText(item.getName());
|
||||
iconLabel.setGraphic(new ImageView(item.getFxImage()));
|
||||
setGraphic(gridPane);
|
||||
//The name is shown in a nested label, so expose it as the cell's accessible text
|
||||
setAccessibleText(item.getName());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -23,11 +23,21 @@ import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.alias.id.AliasID;
|
||||
import io.github.dsheirer.alias.id.AliasIDType;
|
||||
import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio;
|
||||
import io.github.dsheirer.alias.id.radio.Radio;
|
||||
import io.github.dsheirer.alias.id.radio.RadioRange;
|
||||
import io.github.dsheirer.alias.id.status.UnitStatusID;
|
||||
import io.github.dsheirer.alias.id.status.UserStatusID;
|
||||
import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup;
|
||||
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
|
||||
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.playlist.PlaylistManager;
|
||||
import io.github.dsheirer.protocol.Protocol;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
@@ -39,6 +49,7 @@ import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
@@ -52,6 +63,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -62,7 +74,7 @@ public class AliasViewByIdentifierEditor extends VBox
|
||||
private ComboBox<String> mAliasListNameComboBox;
|
||||
private ComboBox<AliasIDType> mAliasIDTypeComboBox;
|
||||
private TableView<AliasAndIdentifier> mAliasAndIdentifierTableView;
|
||||
private TableColumn<AliasAndIdentifier,String> mIdentifierColumn;
|
||||
private TableColumn<AliasAndIdentifier,AliasID> mIdentifierColumn;
|
||||
private Button mViewAliasButton;
|
||||
private boolean aliasListInvalidated;
|
||||
|
||||
@@ -85,6 +97,7 @@ public class AliasViewByIdentifierEditor extends VBox
|
||||
gridPane.setHgap(10);
|
||||
|
||||
Label aliasListLabel = new Label("Alias List");
|
||||
aliasListLabel.setLabelFor(getAliasListNameComboBox());
|
||||
GridPane.setHalignment(aliasListLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(aliasListLabel, 0, 0);
|
||||
gridPane.getChildren().add(aliasListLabel);
|
||||
@@ -94,6 +107,7 @@ public class AliasViewByIdentifierEditor extends VBox
|
||||
gridPane.getChildren().add(getAliasListNameComboBox());
|
||||
|
||||
Label identifierLabel = new Label("Identifier Type");
|
||||
identifierLabel.setLabelFor(getAliasIDTypeComboBox());
|
||||
GridPane.setHalignment(identifierLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(identifierLabel, 0, 1);
|
||||
gridPane.getChildren().add(identifierLabel);
|
||||
@@ -198,9 +212,29 @@ public class AliasViewByIdentifierEditor extends VBox
|
||||
if(mAliasAndIdentifierTableView == null)
|
||||
{
|
||||
mAliasAndIdentifierTableView = new TableView<>(FXCollections.observableArrayList(AliasAndIdentifier.extractor()));
|
||||
mAliasAndIdentifierTableView.setAccessibleText("Aliases by identifier");
|
||||
mAliasAndIdentifierTableView.setPlaceholder(new Label("No aliases or identifiers available"));
|
||||
mIdentifierColumn = new TableColumn<>("Identifier");
|
||||
mIdentifierColumn.setCellValueFactory(new PropertyValueFactory<>("identifier"));
|
||||
mIdentifierColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().getAliasIdentifier()));
|
||||
mIdentifierColumn.setCellFactory(column -> new TableCell<>()
|
||||
{
|
||||
@Override
|
||||
protected void updateItem(AliasID item, boolean empty)
|
||||
{
|
||||
super.updateItem(item, empty);
|
||||
textProperty().unbind();
|
||||
|
||||
if(empty || item == null)
|
||||
{
|
||||
setText(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
textProperty().bind(item.valueProperty());
|
||||
}
|
||||
}
|
||||
});
|
||||
mIdentifierColumn.setComparator(new AliasIdentifierComparator());
|
||||
mIdentifierColumn.setPrefWidth(350);
|
||||
|
||||
TableColumn<AliasAndIdentifier, String> aliasColumn = new TableColumn<>();
|
||||
@@ -268,6 +302,103 @@ public class AliasViewByIdentifierEditor extends VBox
|
||||
return mAliasIDTypeComboBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares alias identifiers by protocol and then by numeric identifier value(s) so that the identifier column
|
||||
* sorts numerically rather than alphabetically by the formatted display text. Identifiers that don't have a
|
||||
* numeric value fall back to comparing the display text.
|
||||
*/
|
||||
public static class AliasIdentifierComparator implements Comparator<AliasID>
|
||||
{
|
||||
private static final Comparator<Protocol> PROTOCOL_COMPARATOR = Comparator.nullsLast(Comparator.naturalOrder());
|
||||
|
||||
@Override
|
||||
public int compare(AliasID a, AliasID b)
|
||||
{
|
||||
if(a == b)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if(a == null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if(b == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int comparison = PROTOCOL_COMPARATOR.compare(getProtocol(a), getProtocol(b));
|
||||
|
||||
if(comparison == 0)
|
||||
{
|
||||
long[] aValues = getValues(a);
|
||||
long[] bValues = getValues(b);
|
||||
|
||||
if(aValues != null && bValues != null)
|
||||
{
|
||||
for(int x = 0; x < Math.min(aValues.length, bValues.length) && comparison == 0; x++)
|
||||
{
|
||||
comparison = Long.compare(aValues[x], bValues[x]);
|
||||
}
|
||||
|
||||
if(comparison == 0)
|
||||
{
|
||||
comparison = Integer.compare(aValues.length, bValues.length);
|
||||
}
|
||||
}
|
||||
else if(aValues != null)
|
||||
{
|
||||
comparison = -1;
|
||||
}
|
||||
else if(bValues != null)
|
||||
{
|
||||
comparison = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(comparison == 0)
|
||||
{
|
||||
comparison = String.valueOf(a).compareTo(String.valueOf(b));
|
||||
}
|
||||
|
||||
return comparison;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protocol for the identifier, or null if the identifier doesn't have a protocol.
|
||||
*/
|
||||
private static Protocol getProtocol(AliasID id)
|
||||
{
|
||||
return switch(id)
|
||||
{
|
||||
case Talkgroup talkgroup -> talkgroup.getProtocol();
|
||||
case TalkgroupRange talkgroupRange -> talkgroupRange.getProtocol();
|
||||
case Radio radio -> radio.getProtocol();
|
||||
case RadioRange radioRange -> radioRange.getProtocol();
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Numeric values for the identifier, in order of sort significance, or null if the identifier is not numeric.
|
||||
*/
|
||||
private static long[] getValues(AliasID id)
|
||||
{
|
||||
return switch(id)
|
||||
{
|
||||
case P25FullyQualifiedTalkgroup fqt -> new long[]{fqt.getWacn(), fqt.getSystem(), fqt.getValue()};
|
||||
case P25FullyQualifiedRadio fqr -> new long[]{fqr.getWacn(), fqr.getSystem(), fqr.getValue()};
|
||||
case Talkgroup talkgroup -> new long[]{talkgroup.getValue()};
|
||||
case TalkgroupRange range -> new long[]{range.getMinTalkgroup(), range.getMaxTalkgroup()};
|
||||
case Radio radio -> new long[]{radio.getValue()};
|
||||
case RadioRange range -> new long[]{range.getMinRadio(), range.getMaxRadio()};
|
||||
case UnitStatusID status -> new long[]{status.getStatus()};
|
||||
case UserStatusID status -> new long[]{status.getStatus()};
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class AliasAndIdentifier
|
||||
{
|
||||
private Alias mAlias;
|
||||
|
||||
@@ -34,6 +34,7 @@ import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.control.cell.PropertyValueFactory;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
@@ -157,12 +158,14 @@ public class AliasViewByRecordingEditor extends VBox
|
||||
aliasListBox.setAlignment(Pos.CENTER);
|
||||
aliasListBox.setSpacing(5);
|
||||
Label aliasListLabel = new Label("Alias List");
|
||||
aliasListLabel.setLabelFor(getAliasListNameComboBox());
|
||||
aliasListBox.getChildren().addAll(aliasListLabel, getAliasListNameComboBox());
|
||||
|
||||
HBox searchBox = new HBox();
|
||||
searchBox.setAlignment(Pos.CENTER);
|
||||
searchBox.setSpacing(5);
|
||||
Label searchLabel = new Label("Search");
|
||||
searchLabel.setLabelFor(getSearchField());
|
||||
searchBox.getChildren().addAll(searchLabel, getSearchField());
|
||||
|
||||
mTopBox.getChildren().addAll(aliasListBox, searchBox);
|
||||
@@ -228,6 +231,7 @@ public class AliasViewByRecordingEditor extends VBox
|
||||
if(mNoRecordAliasTableView == null)
|
||||
{
|
||||
mNoRecordAliasTableView = new TableView<>();
|
||||
mNoRecordAliasTableView.setAccessibleText("Don't record audio aliases");
|
||||
mNoRecordAliasTableView.setMaxHeight(Double.MAX_VALUE);
|
||||
mNoRecordAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
|
||||
@@ -266,6 +270,7 @@ public class AliasViewByRecordingEditor extends VBox
|
||||
if(mRecordAliasTableView == null)
|
||||
{
|
||||
mRecordAliasTableView = new TableView<>();
|
||||
mRecordAliasTableView.setAccessibleText("Record audio aliases");
|
||||
mRecordAliasTableView.setMaxHeight(Double.MAX_VALUE);
|
||||
mRecordAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
|
||||
@@ -307,6 +312,8 @@ public class AliasViewByRecordingEditor extends VBox
|
||||
mAddButton.setDisable(true);
|
||||
mAddButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mAddButton.setGraphic(new IconNode(FontAwesome.ANGLE_RIGHT));
|
||||
mAddButton.setAccessibleText("Record audio for selected alias");
|
||||
mAddButton.setTooltip(new Tooltip("Record audio for selected alias"));
|
||||
mAddButton.setAlignment(Pos.CENTER);
|
||||
mAddButton.setOnAction(event -> {
|
||||
Alias selectedAlias = getNoRecordAliasTableView().getSelectionModel().getSelectedItem();
|
||||
@@ -330,6 +337,8 @@ public class AliasViewByRecordingEditor extends VBox
|
||||
mAddAllButton.setDisable(true);
|
||||
mAddAllButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mAddAllButton.setGraphic(new IconNode(FontAwesome.ANGLE_DOUBLE_RIGHT));
|
||||
mAddAllButton.setAccessibleText("Record audio for all selected aliases");
|
||||
mAddAllButton.setTooltip(new Tooltip("Record audio for all selected aliases"));
|
||||
mAddAllButton.setAlignment(Pos.CENTER);
|
||||
mAddAllButton.setOnAction(event -> {
|
||||
List<Alias> selectedAliases =
|
||||
@@ -358,6 +367,8 @@ public class AliasViewByRecordingEditor extends VBox
|
||||
mRemoveButton.setDisable(true);
|
||||
mRemoveButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mRemoveButton.setGraphic(new IconNode(FontAwesome.ANGLE_LEFT));
|
||||
mRemoveButton.setAccessibleText("Don't record audio for selected alias");
|
||||
mRemoveButton.setTooltip(new Tooltip("Don't record audio for selected alias"));
|
||||
mRemoveButton.setAlignment(Pos.CENTER);
|
||||
mRemoveButton.setOnAction(event -> {
|
||||
Alias selectedAlias = getRecordAliasTableView().getSelectionModel().getSelectedItem();
|
||||
@@ -381,6 +392,8 @@ public class AliasViewByRecordingEditor extends VBox
|
||||
mRemoveAllButton.setDisable(true);
|
||||
mRemoveAllButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mRemoveAllButton.setGraphic(new IconNode(FontAwesome.ANGLE_DOUBLE_LEFT));
|
||||
mRemoveAllButton.setAccessibleText("Don't record audio for all selected aliases");
|
||||
mRemoveAllButton.setTooltip(new Tooltip("Don't record audio for all selected aliases"));
|
||||
mRemoveAllButton.setAlignment(Pos.CENTER);
|
||||
mRemoveAllButton.setOnAction(event -> {
|
||||
List<Alias> selectedAliases =
|
||||
|
||||
@@ -66,6 +66,7 @@ public class BeepEditor extends ActionEditor<BeepAction>
|
||||
int row = 0;
|
||||
|
||||
Label intervalLabel = new Label(("Beep"));
|
||||
intervalLabel.setLabelFor(getIntervalComboBox());
|
||||
GridPane.setHalignment(intervalLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(intervalLabel, 0, row);
|
||||
gridPane.getChildren().add(intervalLabel);
|
||||
@@ -80,6 +81,7 @@ public class BeepEditor extends ActionEditor<BeepAction>
|
||||
GridPane.setConstraints(getSecondsLabel(), 3, row);
|
||||
GridPane.setHgrow(getSecondsLabel(), Priority.ALWAYS);
|
||||
gridPane.getChildren().add(getSecondsLabel());
|
||||
getSecondsLabel().setLabelFor(getPeriodTextField());
|
||||
|
||||
GridPane.setConstraints(getTestButton(), 4, row);
|
||||
gridPane.getChildren().add(getTestButton());
|
||||
@@ -155,6 +157,7 @@ public class BeepEditor extends ActionEditor<BeepAction>
|
||||
if(mTestButton == null)
|
||||
{
|
||||
mTestButton = new Button("Test");
|
||||
mTestButton.setAccessibleText("Test beep");
|
||||
mTestButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mTestButton.setOnAction(event -> test());
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ public class ClipEditor extends ActionEditor<ClipAction>
|
||||
int row = 0;
|
||||
|
||||
Label fileLabel = new Label("Audio File");
|
||||
fileLabel.setLabelFor(getFilePath());
|
||||
GridPane.setHalignment(fileLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(fileLabel, 0, row);
|
||||
gridPane.getChildren().add(fileLabel);
|
||||
@@ -83,6 +84,7 @@ public class ClipEditor extends ActionEditor<ClipAction>
|
||||
gridPane.getChildren().add(getFileChooser());
|
||||
|
||||
Label intervalLabel = new Label(("Play Audio Clip"));
|
||||
intervalLabel.setLabelFor(getIntervalComboBox());
|
||||
GridPane.setHalignment(intervalLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(intervalLabel, 0, ++row);
|
||||
gridPane.getChildren().add(intervalLabel);
|
||||
@@ -97,6 +99,7 @@ public class ClipEditor extends ActionEditor<ClipAction>
|
||||
GridPane.setConstraints(getSecondsLabel(), 3, row);
|
||||
GridPane.setHgrow(getSecondsLabel(), Priority.ALWAYS);
|
||||
gridPane.getChildren().add(getSecondsLabel());
|
||||
getSecondsLabel().setLabelFor(getPeriodTextField());
|
||||
|
||||
GridPane.setConstraints(getTestButton(), 4, row);
|
||||
gridPane.getChildren().add(getTestButton());
|
||||
@@ -223,6 +226,7 @@ public class ClipEditor extends ActionEditor<ClipAction>
|
||||
if(mTestButton == null)
|
||||
{
|
||||
mTestButton = new Button("Test");
|
||||
mTestButton.setAccessibleText("Test audio clip");
|
||||
mTestButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mTestButton.setDisable(true);
|
||||
mTestButton.setOnAction(event -> test());
|
||||
@@ -236,6 +240,7 @@ public class ClipEditor extends ActionEditor<ClipAction>
|
||||
if(mFileChooser == null)
|
||||
{
|
||||
mFileChooser = new Button(("Select ..."));
|
||||
mFileChooser.setAccessibleText("Select audio file");
|
||||
mFileChooser.setMaxWidth(Double.MAX_VALUE);
|
||||
mFileChooser.setDisable(true);
|
||||
mFileChooser.setOnAction(event -> {
|
||||
@@ -278,7 +283,8 @@ public class ClipEditor extends ActionEditor<ClipAction>
|
||||
if(mFilePath == null)
|
||||
{
|
||||
mFilePath = new TextField(PLEASE_SELECT_A_FILE);
|
||||
mFilePath.setDisable(true);
|
||||
//Read-only rather than disabled so that the selected file remains focusable for screen readers
|
||||
mFilePath.setEditable(false);
|
||||
mFilePath.setMaxWidth(Double.MAX_VALUE);
|
||||
mFilePath.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if(newValue != null && !newValue.isEmpty() && !newValue.contentEquals(PLEASE_SELECT_A_FILE))
|
||||
|
||||
@@ -71,6 +71,7 @@ public class ScriptEditor extends ActionEditor<ScriptAction>
|
||||
int row = 0;
|
||||
|
||||
Label fileLabel = new Label("Script File");
|
||||
fileLabel.setLabelFor(getFilePath());
|
||||
GridPane.setHalignment(fileLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(fileLabel, 0, row);
|
||||
gridPane.getChildren().add(fileLabel);
|
||||
@@ -83,6 +84,7 @@ public class ScriptEditor extends ActionEditor<ScriptAction>
|
||||
gridPane.getChildren().add(getFileChooser());
|
||||
|
||||
Label intervalLabel = new Label(("Run Script"));
|
||||
intervalLabel.setLabelFor(getIntervalComboBox());
|
||||
GridPane.setHalignment(intervalLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(intervalLabel, 0, ++row);
|
||||
gridPane.getChildren().add(intervalLabel);
|
||||
@@ -97,6 +99,7 @@ public class ScriptEditor extends ActionEditor<ScriptAction>
|
||||
GridPane.setConstraints(getSecondsLabel(), 3, row);
|
||||
GridPane.setHgrow(getSecondsLabel(), Priority.ALWAYS);
|
||||
gridPane.getChildren().add(getSecondsLabel());
|
||||
getSecondsLabel().setLabelFor(getPeriodTextField());
|
||||
|
||||
GridPane.setConstraints(getTestButton(), 4, row);
|
||||
gridPane.getChildren().add(getTestButton());
|
||||
@@ -223,6 +226,7 @@ public class ScriptEditor extends ActionEditor<ScriptAction>
|
||||
if(mTestButton == null)
|
||||
{
|
||||
mTestButton = new Button("Test");
|
||||
mTestButton.setAccessibleText("Test script");
|
||||
mTestButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mTestButton.setDisable(true);
|
||||
mTestButton.setOnAction(event -> test());
|
||||
@@ -236,6 +240,7 @@ public class ScriptEditor extends ActionEditor<ScriptAction>
|
||||
if(mFileChooser == null)
|
||||
{
|
||||
mFileChooser = new Button(("Select ..."));
|
||||
mFileChooser.setAccessibleText("Select script file");
|
||||
mFileChooser.setMaxWidth(Double.MAX_VALUE);
|
||||
mFileChooser.setDisable(true);
|
||||
mFileChooser.setOnAction(event -> {
|
||||
@@ -278,7 +283,8 @@ public class ScriptEditor extends ActionEditor<ScriptAction>
|
||||
if(mFilePath == null)
|
||||
{
|
||||
mFilePath = new TextField(PLEASE_SELECT_A_FILE);
|
||||
mFilePath.setDisable(true);
|
||||
//Read-only rather than disabled so that the selected file remains focusable for screen readers
|
||||
mFilePath.setEditable(false);
|
||||
mFilePath.setMaxWidth(Double.MAX_VALUE);
|
||||
mFilePath.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if(newValue != null && !newValue.isEmpty() && !newValue.contentEquals(PLEASE_SELECT_A_FILE))
|
||||
|
||||
@@ -44,6 +44,7 @@ public class DcsEditor extends IdentifierEditor<Dcs>
|
||||
gridPane.setHgap(5);
|
||||
|
||||
Label typeLabel = new Label("DCS Code");
|
||||
typeLabel.setLabelFor(getDCSCodeComboBox());
|
||||
GridPane.setConstraints(typeLabel, 0, 0);
|
||||
gridPane.getChildren().add(typeLabel);
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ public class EsnEditor extends IdentifierEditor<Esn>
|
||||
gridPane.setHgap(5);
|
||||
|
||||
Label typeLabel = new Label("ESN");
|
||||
typeLabel.setLabelFor(getEsnField());
|
||||
GridPane.setConstraints(typeLabel, 0, 0);
|
||||
gridPane.getChildren().add(typeLabel);
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ public class LojackEditor extends IdentifierEditor<LoJackFunctionAndID>
|
||||
gridPane.setHgap(5);
|
||||
|
||||
Label typeLabel = new Label("LoJack Function");
|
||||
typeLabel.setLabelFor(getFunctionComboBox());
|
||||
GridPane.setConstraints(typeLabel, 0, 0);
|
||||
gridPane.getChildren().add(typeLabel);
|
||||
|
||||
@@ -57,6 +58,10 @@ public class LojackEditor extends IdentifierEditor<LoJackFunctionAndID>
|
||||
gridPane.getChildren().add(getIdentifierField());
|
||||
|
||||
Label helpLabel = new Label("Format: 5 numbers, characters or (*)wildcard - e.g. AB*12");
|
||||
//The identifier field has no label of its own, so the help label provides its accessible name and help text
|
||||
helpLabel.setLabelFor(getIdentifierField());
|
||||
helpLabel.setAccessibleText("LoJack ID");
|
||||
getIdentifierField().setAccessibleHelp(helpLabel.getText());
|
||||
GridPane.setConstraints(helpLabel, 3, 0);
|
||||
gridPane.getChildren().add(helpLabel);
|
||||
|
||||
|
||||
@@ -76,11 +76,13 @@ public class NXDNTypeDEditor<T extends AliasID & INXDNTypeD> extends IdentifierE
|
||||
gridPane.add(new Label("NXDN Type-D"), 0, 0);
|
||||
|
||||
Label homeRepeaterLabel = new Label("Home Repeater");
|
||||
homeRepeaterLabel.setLabelFor(mHomeRepeaterField);
|
||||
GridPane.setHalignment(homeRepeaterLabel, HPos.RIGHT);
|
||||
gridPane.add(homeRepeaterLabel, 1, 0);
|
||||
gridPane.add(mHomeRepeaterField, 2, 0);
|
||||
|
||||
Label identifierFieldLabel = new Label(identifierLabel);
|
||||
identifierFieldLabel.setLabelFor(mIdentifierField);
|
||||
GridPane.setHalignment(identifierFieldLabel, HPos.RIGHT);
|
||||
gridPane.add(identifierFieldLabel, 3, 0);
|
||||
gridPane.add(mIdentifierField, 4, 0);
|
||||
|
||||
@@ -89,15 +89,21 @@ public class NXDNTypeDRangeEditor<T extends AliasID & INXDNTypeDRange> extends I
|
||||
gridPane.add(new Label("NXDN Type-D"), 0, 0);
|
||||
|
||||
Label homeRepeaterLabel = new Label("Home Repeater");
|
||||
homeRepeaterLabel.setLabelFor(mHomeRepeaterField);
|
||||
GridPane.setHalignment(homeRepeaterLabel, HPos.RIGHT);
|
||||
gridPane.add(homeRepeaterLabel, 1, 0);
|
||||
gridPane.add(mHomeRepeaterField, 2, 0);
|
||||
|
||||
Label rangeFieldLabel = new Label(rangeLabel);
|
||||
rangeFieldLabel.setLabelFor(mMinField);
|
||||
rangeFieldLabel.setAccessibleText(rangeLabel + " start");
|
||||
GridPane.setHalignment(rangeFieldLabel, HPos.RIGHT);
|
||||
gridPane.add(rangeFieldLabel, 3, 0);
|
||||
gridPane.add(mMinField, 4, 0);
|
||||
gridPane.add(new Label("-"), 5, 0);
|
||||
Label dashLabel = new Label("-");
|
||||
dashLabel.setLabelFor(mMaxField);
|
||||
dashLabel.setAccessibleText(rangeLabel + " end");
|
||||
gridPane.add(dashLabel, 5, 0);
|
||||
gridPane.add(mMaxField, 6, 0);
|
||||
|
||||
getChildren().add(gridPane);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user