18 Commits

Author SHA1 Message Date
vladimir 3ad40a00da calcc: port eu2av robustness patches for PureSignal calibration
Three fixes from Thetis-Enhanced (Yurij eu2av), measured on Orion MK2 /
Anvelina PRO3 hardware:

- drop overrange samples (env_TX*hw_scale > 1.0) before the cubic
  xbuilder fit - they distort the fit and produce a wrong rx_scale
- optional median-ratio + MAD outlier rejection before the fit,
  controlled by new SetPSOutlierSigma (0 = off)
- fallback rx_scale estimate from the top amplitude intervals when the
  xbuilder fit fails or is rejected by rxscheck

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 10:47:44 +03:00
Uladzimir Karpenka c1964817dd wfm: compile wfmd/wfmmod in the Windows build
Makefile.windows arrived from main, which predates the wfm module, so its
SOURCES list omitted wfmd.c/wfmmod.c even though RXA.c/TXA.c reference their
symbols -- the MinGW link failed on the missing objects. Add both to SOURCES,
matching Makefile and Makefile.android.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:03:03 +03:00
Uladzimir Karpenka d24b5ae316 Merge remote-tracking branch 'origin/main' into wfm 2026-07-15 10:50:15 +03:00
Uladzimir Karpenka 65cb3c386e wfm: add a wideband FM modulator and demodulator
Model the pair on fmd.c / fmmod.c, but with the parts that a 75 kHz
deviation forces:

  - the demodulator discriminates with arg(x[n] * conj(x[n-1])) rather
    than a PLL; an omegaN in the tens of kHz cannot track 75 kHz.
  - emphasis is a one-pole RC (tau 75 us) on both ends, not fmd's 1/f
    fc_impulse FIR.  A 1/f FIR from f_low = 20 Hz would sit ~+57 dB at
    20 Hz, where broadcast FM specifies flat below the corner.
  - TXA_WFM leaves the shared preemph block off; wfmmod carries its own.
  - wfmmod clamps bp_fc = deviation + f_high to 0.45 * samplerate, since
    +/-90 kHz exceeds Nyquist at the rates the narrowband modes use.

Scope is mono: no 19 kHz pilot, no 38 kHz stereo subcarrier, no RDS.

Both mode enums are appended to so the ABI stays stable for clients.
The JNI bindings are deliberately left alone.

Verified against a wfmmod -> wfmd loopback: the discriminator is exact,
the dc-removal one-pole tracks |H_lp(f)| * ain * sdelta to ratio 1.000,
and a 700 + 1900 Hz two-tone comes back with THD+N ~ 3e-5 % at +/-37.5
kHz deviation.  Note the demodulator aliases unless samplerate exceeds
2 * deviation * |aud|max, so 192 kHz is the practical floor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 07:43:07 +03:00
vladimir bb7e0b3df6 add gitignore third_party 2026-06-12 21:44:25 +03:00
vladimir 4313006fa6 Auto-detect Android NDK and javac paths in Makefile.android
Remove hardcoded /home/vladimir and /opt paths. NDK is now resolved via
ANDROID_NDK_HOME, ANDROID_SDK_ROOT/ANDROID_HOME, or the default SDK
location for Linux/macOS (latest version picked automatically). javac is
resolved via JAVA_HOME, system PATH, or Android Studio JBR on Linux/macOS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 21:31:15 +03:00
vladimir 4f44118299 Remove committed build artifacts from third_party and fix Android FFTW cross-compile
- Untrack all .o and .a files under third_party/ (rnnoise, libspecbleach)
- Add third_party/**/*.o and third_party/**/*.a to .gitignore
- Fix FFTW configure cross-compilation: add $(strip ...) around fftw_host
  macro call and explicit --build flag so configure detects cross-compile
  correctly (GNU make backslash-continuation in define blocks inserts a
  leading space, causing --host= arm-... to be parsed as empty --host)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 21:27:13 +03:00
vladimir edd8991a3c Enable AVX2 for FFTW Windows build to suppress SSE2-only warning
FFTW emits '#warning Only SSE and SSE2 are available' when cross-compiling
with a generic x86_64 target: the configure test for AVX intrinsics fails
without an explicit -mavx flag, so FFTW silently falls back to SSE2.

Fix: pass CFLAGS="-march=haswell" to configure, which enables SSE2/AVX/AVX2/FMA.
Add --enable-avx2 alongside existing --enable-avx.
FFTW_MARCH is overridable for older CPU targets:
  make -f Makefile.windows FFTW_MARCH=-march=sandybridge  # AVX only, 2011+
  make -f Makefile.windows FFTW_MARCH=-march=core2        # SSSE3, 2007+

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 13:39:24 +03:00
vladimir a3385bd1f9 Fix parallel build race: DLL link must wait for fftw-float install
With -jN, the DLL link step could start before fftw-float finished
installing libfftw3f.dll.a, since $(DLL) only had an implicit ordering
through .o compilation (which needs FFTW_LIB_D but not FFTW_LIB_F).

Add both FFTW_LIB_D and FFTW_LIB_F as explicit prerequisites of $(DLL).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 13:35:04 +03:00
vladimir b7d156ea40 Compile rnnoise and libspecbleach directly into obj_win/ for Windows
Sub-make of third_party/ reported 'up to date' when Linux ELF objects
were present from a prior host build, causing the linker to receive
ELF archives instead of PE/COFF and producing undefined reference errors.

Fix: compile rnnoise and libspecbleach sources directly into obj_win/
with explicit pattern rules, producing lib_win/librnnoise.a and
lib_win/libspecbleach.a. These are fully isolated from third_party/
and never conflict with the Linux build artifacts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 13:17:32 +03:00
vladimir d997990ceb Fix two Windows cross-compilation errors in analyzer.c
WDSP_FPE_GUARD: the macro is defined only in linux_port.h under
#if defined(linux)||defined(__APPLE__). On the _WIN32 path it was
completely undefined. Added a fallback no-op definition in comm.h
guarded by #ifndef so it applies to Windows (and any future platform
that doesn't include linux_port.h).

volatile int* vs volatile LONG*: Win32 Interlocked functions expect
volatile LONG* (= volatile long*). The dispatcher field is volatile int.
On Windows LLP64 both are 32-bit so the operation is correct, but GCC 14
promotes this mismatch from warning to error. Suppressed with
-Wno-incompatible-pointer-types in Makefile.windows, consistent with
how MSVC handles it silently.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 12:54:56 +03:00
vladimir a77dbbb6e5 Fix Windows.h case on Linux: use lowercase windows.h
On Linux (case-sensitive filesystem) MinGW-w64 installs the header as
windows.h (lowercase) while comm.h included <Windows.h> (capital W),
causing a fatal compile error when cross-compiling for Windows.

Changed to <windows.h> which works on both Linux/MinGW-w64 and native
Windows/MSVC (Windows header includes are case-insensitive on NTFS).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 12:43:45 +03:00
vladimir 90c906eb3a Fix missing Windows.h: require full mingw-w64 package, not just compiler
gcc-mingw-w64-x86-64 on Debian/Ubuntu ships only the compiler binary,
without Windows API headers — causing fatal error: Windows.h not found.
The fix is to install the mingw-w64 meta-package which includes headers,
CRT and runtime libraries.

Added a check-tools probe that compiles #include <windows.h> and prints
a clear error with the correct package name if headers are missing.
Updated README with a warning about the incomplete package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 12:33:13 +03:00
vladimir f29970542b Pass CC explicitly to FFTW configure to suppress cross-tools warning
Without CC=, autoconf issues "using cross tools not prefixed with host
triplet" because it cannot match the detected compiler against --host.
Passing CC=$(MINGW_PREFIX)-gcc directly resolves the ambiguity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 12:26:39 +03:00
vladimir 69ea631daa Fix parallel build race condition in Makefile.windows
With -jN, make started third-party and fftw targets in parallel.
libspecbleach depended on FFTW_HEADER which has no explicit rule
(it is a side effect of make install), causing 'No rule to make target'.
Object compilation also could start before FFTW headers were ready.

Fix: depend on FFTW_LIB_D (has an explicit rule) instead of FFTW_HEADER,
and add FFTW_LIB_D as order-only prerequisite for .o compilation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 12:24:39 +03:00
vladimir 02926b36ce Auto-download FFTW source for Android build
Makefile.android now downloads fftw-3.3.11.tar.gz automatically instead
of requiring the user to place sources in third_party/fftw/ manually.
The FFTW_SRC variable can still be overridden to use an existing copy.
Added distclean target. Updated README accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 12:15:59 +03:00
vladimir 5bbf9698a8 Build FFTW from source instead of downloading pre-built DLLs
FFTW 3.3.11 has no pre-built Windows binaries, so Makefile.windows now
downloads the source tarball and cross-compiles it twice with MinGW-w64:
once for double precision and once for float (--enable-float), installing
both into third_party/fftw-win64/. Links with -lfftw3/-lfftw3f via
libtool-generated import libs instead of dlltool-generated ones.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 12:13:35 +03:00
vladimir ec93fbfcbb Add Windows cross-compilation support via MinGW-w64
Makefile.windows builds libwdsp.dll and libwdsp.a for 64-bit Windows
on Linux. FFTW 3.3.11 Windows binaries are downloaded automatically
from fftw.org and import libraries are generated with dlltool.
README updated with Windows build instructions and FFTW version bump.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 10:46:51 +03:00
57 changed files with 1497 additions and 45 deletions
+4
View File
@@ -7,6 +7,10 @@ java/build/
# third_party — only fftw is downloaded manually, rnnoise and libspecbleach are tracked # third_party — only fftw is downloaded manually, rnnoise and libspecbleach are tracked
third_party/fftw/ third_party/fftw/
third_party/**/*.o
third_party/**/*.a
third_party/fftw-3.3.11.tar.gz
third_party/fftw-3.3.11/
# macOS # macOS
.DS_Store .DS_Store
+2
View File
@@ -144,6 +144,8 @@ utilities.c \
varsamp.c \ varsamp.c \
version.c \ version.c \
wcpAGC.c \ wcpAGC.c \
wfmd.c \
wfmmod.c \
wisdom.c \ wisdom.c \
zetaHat.c zetaHat.c
+60 -22
View File
@@ -5,17 +5,39 @@
# Optional overrides: # Optional overrides:
# ANDROID_NDK=/path/to/ndk ANDROID_API=24 ANDROID_ABIS="arm64-v8a x86_64" # ANDROID_NDK=/path/to/ndk ANDROID_API=24 ANDROID_ABIS="arm64-v8a x86_64"
# #
# Requires FFTW source in third_party/fftw/ # FFTW source is downloaded and extracted automatically.
# Download from https://www.fftw.org/download.html and extract so that # Override FFTW_SRC to use an existing directory.
# third_party/fftw/configure exists.
ANDROID_NDK ?= /home/vladimir/Android/Sdk/ndk/29.0.14206865 # Auto-detect NDK path (can always be overridden by setting ANDROID_NDK explicitly):
# 1. ANDROID_NDK_HOME environment variable
# 2. Latest NDK under ANDROID_SDK_ROOT or ANDROID_HOME
# 3. Default SDK locations: ~/Android/Sdk (Linux) or ~/Library/Android/sdk (macOS)
ifeq ($(origin ANDROID_NDK),undefined)
ifdef ANDROID_NDK_HOME
ANDROID_NDK := $(ANDROID_NDK_HOME)
else
_SDK_ROOT := $(or $(ANDROID_SDK_ROOT),$(ANDROID_HOME),\
$(wildcard $(HOME)/Android/Sdk),\
$(wildcard $(HOME)/Library/Android/sdk))
ifdef _SDK_ROOT
ANDROID_NDK := $(lastword $(sort $(wildcard $(_SDK_ROOT)/ndk/*)))
endif
endif
endif
ifndef ANDROID_NDK
$(error Cannot find Android NDK. Set ANDROID_NDK, ANDROID_NDK_HOME, ANDROID_SDK_ROOT, or ANDROID_HOME)
endif
ANDROID_API ?= 24 ANDROID_API ?= 24
ANDROID_ABIS ?= arm64-v8a armeabi-v7a x86_64 ANDROID_ABIS ?= arm64-v8a armeabi-v7a x86_64
ANDROID_HOST_TAG ?= $(shell uname -s | tr '[:upper:]' '[:lower:]' | sed 's/darwin/darwin/;s/linux/linux/')-$(shell uname -m | sed 's/aarch64/arm64/;s/x86_64/x86_64/') ANDROID_HOST_TAG ?= $(shell uname -s | tr '[:upper:]' '[:lower:]' | sed 's/darwin/darwin/;s/linux/linux/')-$(shell uname -m | sed 's/aarch64/arm64/;s/x86_64/x86_64/')
JBR_BIN ?= /opt/android-studio/jbr/bin # Auto-detect javac: JAVA_HOME > system PATH > Android Studio JBR (Linux/macOS)
JAVAC ?= $(JBR_BIN)/javac _JAVAC_CANDIDATES := \
$(if $(JAVA_HOME),$(wildcard $(JAVA_HOME)/bin/javac)) \
$(shell command -v javac 2>/dev/null) \
$(wildcard /opt/android-studio/jbr/bin/javac) \
$(wildcard /Applications/Android\ Studio.app/Contents/jbr/Contents/Home/bin/javac)
JAVAC ?= $(firstword $(_JAVAC_CANDIDATES))
TOOLCHAIN := $(ANDROID_NDK)/toolchains/llvm/prebuilt/$(ANDROID_HOST_TAG) TOOLCHAIN := $(ANDROID_NDK)/toolchains/llvm/prebuilt/$(ANDROID_HOST_TAG)
@@ -24,7 +46,10 @@ COMMON_CPPFLAGS ?= -I. -I third_party/rnnoise/include -I third_party/libspecblea
ANDROID_JNI_CFLAGS ?= -std=gnu89 -Wno-implicit-function-declaration -Wno-int-conversion \ ANDROID_JNI_CFLAGS ?= -std=gnu89 -Wno-implicit-function-declaration -Wno-int-conversion \
-Wno-incompatible-pointer-types -Wno-incompatible-pointer-types-discards-qualifiers -Wno-incompatible-pointer-types -Wno-incompatible-pointer-types-discards-qualifiers
FFTW_SRC ?= third_party/fftw FFTW_VERSION := 3.3.11
FFTW_TAR := fftw-$(FFTW_VERSION).tar.gz
FFTW_URL := https://fftw.org/pub/fftw/$(FFTW_TAR)
FFTW_SRC ?= third_party/fftw-$(FFTW_VERSION)
FFTW_MAKEJOBS ?= $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) FFTW_MAKEJOBS ?= $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
WDSP_SOURCES = amd.c \ WDSP_SOURCES = amd.c \
@@ -94,6 +119,8 @@ utilities.c \
varsamp.c \ varsamp.c \
version.c \ version.c \
wcpAGC.c \ wcpAGC.c \
wfmd.c \
wfmmod.c \
wisdom.c \ wisdom.c \
zetaHat.c zetaHat.c
@@ -148,9 +175,9 @@ ANDROID_OBJ_ROOT := obj/android
ANDROID_LIB_ROOT := lib/android ANDROID_LIB_ROOT := lib/android
ANDROID_JAVA_OUT := lib/android/java ANDROID_JAVA_OUT := lib/android/java
.PHONY: all clean java-classes check-fftw-src .PHONY: all clean distclean java-classes
all: check-fftw-src \ all: $(FFTW_SRC)/configure \
$(foreach abi,$(ANDROID_ABIS),\ $(foreach abi,$(ANDROID_ABIS),\
$(ANDROID_LIB_ROOT)/$(abi)/libfftw3.so \ $(ANDROID_LIB_ROOT)/$(abi)/libfftw3.so \
$(ANDROID_LIB_ROOT)/$(abi)/libfftw3f.so \ $(ANDROID_LIB_ROOT)/$(abi)/libfftw3f.so \
@@ -158,15 +185,21 @@ all: check-fftw-src \
$(ANDROID_LIB_ROOT)/$(abi)/libwdspj.so) \ $(ANDROID_LIB_ROOT)/$(abi)/libwdspj.so) \
java-classes java-classes
check-fftw-src: # ── Автоматическое получение FFTW ────────────────────────────────────────────
@test -f $(FFTW_SRC)/configure || { \
echo ""; \ third_party/$(FFTW_TAR):
echo "ERROR: FFTW source not found at $(FFTW_SRC)/configure"; \ mkdir -p third_party
echo "Download and extract FFTW from https://www.fftw.org/download.html"; \ @echo ">>> Скачиваем FFTW $(FFTW_VERSION)..."
echo "so that $(FFTW_SRC)/configure exists."; \ @if command -v wget >/dev/null 2>&1; then \
echo ""; \ wget -q --show-progress -O $@ "$(FFTW_URL)"; \
exit 1; \ else \
} curl -L --progress-bar -o $@ "$(FFTW_URL)"; \
fi
$(FFTW_SRC)/configure: third_party/$(FFTW_TAR)
@echo ">>> Распаковываем FFTW..."
tar xf $< -C third_party/
@touch $@
# abi_target: compiler triple for NDK clang wrapper # abi_target: compiler triple for NDK clang wrapper
define abi_target define abi_target
@@ -226,11 +259,12 @@ ABI_$(1)_WDSP_OBJS := $$(patsubst %.c,$$(ABI_$(1)_OBJDIR)/%.o,$$(ALL_C_SOURCES))
ABI_$(1)_JNI_OBJ := $$(ABI_$(1)_OBJDIR)/$(JNI_SOURCE:.c=.o) ABI_$(1)_JNI_OBJ := $$(ABI_$(1)_OBJDIR)/$(JNI_SOURCE:.c=.o)
# --- Build fftw3 (double) --- # --- Build fftw3 (double) ---
$$(ABI_$(1)_FFTW_STAMP): $$(ABI_$(1)_FFTW_STAMP): $(FFTW_SRC)/configure
@mkdir -p $$(ABI_$(1)_OBJDIR)/fftw-build $$(ABI_$(1)_FFTW_PREFIX) @mkdir -p $$(ABI_$(1)_OBJDIR)/fftw-build $$(ABI_$(1)_FFTW_PREFIX)
cd $$(ABI_$(1)_OBJDIR)/fftw-build && \ cd $$(ABI_$(1)_OBJDIR)/fftw-build && \
$(abspath $(FFTW_SRC))/configure \ $(abspath $(FFTW_SRC))/configure \
--host=$(call fftw_host,$(1)) \ --build=$(shell uname -m)-linux-gnu \
--host=$(strip $(call fftw_host,$(1))) \
CC="$$(ABI_$(1)_CC)" \ CC="$$(ABI_$(1)_CC)" \
CFLAGS="-fPIC" \ CFLAGS="-fPIC" \
--prefix=$(abspath $$(ABI_$(1)_FFTW_PREFIX)) \ --prefix=$(abspath $$(ABI_$(1)_FFTW_PREFIX)) \
@@ -240,11 +274,12 @@ $$(ABI_$(1)_FFTW_STAMP):
@touch $$@ @touch $$@
# --- Build fftw3f (float/single) --- # --- Build fftw3f (float/single) ---
$$(ABI_$(1)_FFTWF_STAMP): $$(ABI_$(1)_FFTWF_STAMP): $(FFTW_SRC)/configure
@mkdir -p $$(ABI_$(1)_OBJDIR)/fftwf-build $$(ABI_$(1)_FFTWF_PREFIX) @mkdir -p $$(ABI_$(1)_OBJDIR)/fftwf-build $$(ABI_$(1)_FFTWF_PREFIX)
cd $$(ABI_$(1)_OBJDIR)/fftwf-build && \ cd $$(ABI_$(1)_OBJDIR)/fftwf-build && \
$(abspath $(FFTW_SRC))/configure \ $(abspath $(FFTW_SRC))/configure \
--host=$(call fftw_host,$(1)) \ --build=$(shell uname -m)-linux-gnu \
--host=$(strip $(call fftw_host,$(1))) \
CC="$$(ABI_$(1)_CC)" \ CC="$$(ABI_$(1)_CC)" \
CFLAGS="-fPIC" \ CFLAGS="-fPIC" \
--prefix=$(abspath $$(ABI_$(1)_FFTWF_PREFIX)) \ --prefix=$(abspath $$(ABI_$(1)_FFTWF_PREFIX)) \
@@ -311,3 +346,6 @@ java-classes: $(JAVA_SOURCE)
clean: clean:
rm -rf $(ANDROID_OBJ_ROOT) $(ANDROID_LIB_ROOT) rm -rf $(ANDROID_OBJ_ROOT) $(ANDROID_LIB_ROOT)
distclean: clean
-rm -rf $(FFTW_SRC) third_party/$(FFTW_TAR)
+294
View File
@@ -0,0 +1,294 @@
#
# Makefile.windows — кросс-компиляция WDSP для 64-bit Windows на Linux (MinGW-w64)
#
# Использование:
# make -f Makefile.windows # DLL + статическая библиотека
# make -f Makefile.windows dll # только DLL
# make -f Makefile.windows static # только статическая библиотека
# make -f Makefile.windows clean # удалить артефакты сборки
# make -f Makefile.windows distclean # + удалить собранный и скачанный FFTW
#
# Зависимости (установить до запуска):
# Arch: sudo pacman -S mingw-w64-gcc (тянет mingw-w64-headers автоматически)
# Debian: sudo apt install mingw-w64 (мета-пакет: компилятор + заголовки + CRT)
# Fedora: sudo dnf install mingw64-gcc (тянет mingw64-headers автоматически)
# + wget или curl, tar
#
MINGW_PREFIX ?= x86_64-w64-mingw32
CC := $(MINGW_PREFIX)-gcc
AR := $(MINGW_PREFIX)-ar
RANLIB := $(MINGW_PREFIX)-ranlib
CFLAGS ?= -O3 -Wno-parentheses -Wno-incompatible-pointer-types
LDFLAGS ?=
# ── FFTW: сборка из исходников ────────────────────────────────────────────────
FFTW_VERSION := 3.3.11
FFTW_TAR := fftw-$(FFTW_VERSION).tar.gz
FFTW_URL := https://fftw.org/pub/fftw/$(FFTW_TAR)
# Абсолютные пути — нужны для configure --prefix и sub-make -C
FFTW_SRC := $(CURDIR)/third_party/fftw-$(FFTW_VERSION)
FFTW_INST := $(CURDIR)/third_party/fftw-win64
# Раздельные build-директории для double и float precision
FFTW_BUILD_D := $(FFTW_INST)/build-double
FFTW_BUILD_F := $(FFTW_INST)/build-float
# FFTW_MARCH задаёт минимальный набор инструкций для Windows-бинарника.
# haswell (2013+) гарантирует AVX2+FMA — оптимальный выбор для SDR на современном ПК.
# Для совместимости со старыми машинами замените на -march=sandybridge (AVX, 2011+)
# или -march=core2 (SSSE3, 2007+).
FFTW_MARCH ?= -march=haswell
FFTW_CONF_COMMON = \
--host=$(MINGW_PREFIX) \
CC=$(CC) \
CFLAGS="$(FFTW_MARCH)" \
--prefix=$(FFTW_INST) \
--enable-shared \
--disable-static \
--with-our-malloc \
--enable-sse2 \
--enable-avx \
--enable-avx2 \
--disable-fortran \
--quiet
# Sentinel-файлы для отслеживания готовности FFTW
FFTW_HEADER := $(FFTW_INST)/include/fftw3.h
FFTW_LIB_D := $(FFTW_INST)/lib/libfftw3.dll.a
FFTW_LIB_F := $(FFTW_INST)/lib/libfftw3f.dll.a
FFTW_DLL_D := $(FFTW_INST)/bin/libfftw3-3.dll
FFTW_DLL_F := $(FFTW_INST)/bin/libfftw3f-3.dll
# ── Выходные директории и файлы ───────────────────────────────────────────────
OBJDIR := obj_win
OUTDIR := lib_win
DLL := $(OUTDIR)/libwdsp.dll
IMPLIB := $(OUTDIR)/libwdsp.dll.a
STATIC_LIB := $(OUTDIR)/libwdsp.a
# ── Исходники (копия из основного Makefile) ───────────────────────────────────
SOURCES = \
amd.c ammod.c amsq.c analyzer.c anf.c anr.c apfshadow.c \
bandpass.c calcc.c calculus.c cblock.c cfcomp.c cfir.c \
channel.c cmath.c compress.c delay.c dexp.c div.c doublepole.c \
eer.c emnr.c emph.c eq.c fcurve.c FDnoiseIQ.c fir.c firmin.c \
fmd.c fmmod.c fmsq.c gain.c gaussian.c gen.c icfir.c iir.c \
impulse_cache.c iobuffs.c iqc.c linux_port.c lmath.c main.c \
matchedCW.c meter.c meterlog10.c nbp.c nob.c nobII.c osctrl.c \
patchpanel.c resample.c rmatch.c rnnr.c RXA.c sbnr.c sender.c \
shift.c siphon.c slew.c snb.c ssql.c syncbuffs.c TXA.c \
utilities.c varsamp.c version.c wcpAGC.c wfmd.c wfmmod.c wisdom.c zetaHat.c
OBJS := $(addprefix $(OBJDIR)/, $(SOURCES:.c=.o))
# ── NR3/NR4: исходники third-party ────────────────────────────────────────────
#
# Компилируем rnnoise и libspecbleach напрямую в obj_win/, минуя их sub-make.
# Это исключает конфликт с Linux-сборкой: third_party/*.a содержат ELF-объекты,
# и если они "свежее" исходников, sub-make считает их up-to-date и не пересобирает.
RNNOISE_DIR := third_party/rnnoise
SPECBLEACH_DIR := third_party/libspecbleach
RNNOISE_SRCS := \
src/denoise.c src/celt_lpc.c src/kiss_fft.c src/nnet.c \
src/nnet_default.c src/parse_lpcnet_weights.c src/pitch.c \
src/rnn.c src/rnnoise_data.c \
src/rnnoise_data_1.c src/rnnoise_data_2.c src/rnnoise_data_3.c \
src/rnnoise_data_4.c src/rnnoise_data_5.c src/rnnoise_data_6.c \
src/rnnoise_tables.c
SPECBLEACH_SRCS := \
src/processors/specbleach_adenoiser.c \
src/processors/specbleach_denoiser.c \
src/processors/adaptivedenoiser/adaptive_denoiser.c \
src/shared/gain_estimation/gain_estimators.c \
src/shared/noise_estimation/adaptive_noise_estimator.c \
src/shared/pre_estimation/absolute_hearing_thresholds.c \
src/shared/pre_estimation/critical_bands.c \
src/shared/pre_estimation/masking_estimator.c \
src/shared/pre_estimation/noise_scaling_criterias.c \
src/shared/pre_estimation/spectral_smoother.c \
src/shared/pre_estimation/transient_detector.c \
src/shared/post_estimation/noise_floor_manager.c \
src/shared/post_estimation/postfilter.c \
src/shared/post_estimation/spectral_whitening.c \
src/shared/utils/denoise_mixer.c \
src/shared/utils/general_utils.c \
src/shared/utils/spectral_features.c \
src/shared/utils/spectral_utils.c \
src/shared/stft/stft_processor.c \
src/shared/stft/fft_transform.c \
src/shared/stft/stft_buffer.c \
src/shared/stft/stft_windows.c
RNNOISE_OBJS := $(addprefix $(OBJDIR)/rnnoise/, $(RNNOISE_SRCS:.c=.o))
SPECBLEACH_OBJS := $(addprefix $(OBJDIR)/specbleach/, $(SPECBLEACH_SRCS:.c=.o))
RNNOISE_LIB := $(OUTDIR)/librnnoise.a
SPECBLEACH_LIB := $(OUTDIR)/libspecbleach.a
# ── Флаги компиляции ──────────────────────────────────────────────────────────
NR34_INC := -I$(RNNOISE_DIR)/include -I$(SPECBLEACH_DIR)/include
FFTW_INC := -I$(FFTW_INST)/include
COMPILE := $(CC) $(CFLAGS) $(FFTW_INC) $(NR34_INC)
NR34_DEPS := $(RNNOISE_LIB) $(SPECBLEACH_LIB)
# avrt нужен т.к. comm.h включает <avrt.h> под _WIN32
LINK_LIBS := $(NR34_DEPS) \
-L$(FFTW_INST)/lib -lfftw3 -lfftw3f \
-lavrt -lm
# ── Цели ──────────────────────────────────────────────────────────────────────
.PHONY: all dll static fftw fftw-double fftw-float third-party check-tools clean distclean
all: dll static
dll: check-tools fftw third-party $(DLL)
static: check-tools fftw third-party $(STATIC_LIB)
# ── Проверка инструментов ─────────────────────────────────────────────────────
check-tools:
@command -v $(CC) >/dev/null 2>&1 || { \
echo ""; \
echo "ОШИБКА: $(CC) не найден. Установите mingw-w64:"; \
echo " Arch: sudo pacman -S mingw-w64-gcc"; \
echo " Debian: sudo apt install mingw-w64"; \
echo " Fedora: sudo dnf install mingw64-gcc"; \
echo ""; exit 1; }
@echo '#include <windows.h>' | $(CC) -x c - -fsyntax-only -Wno-pragma-once-outside-header 2>/dev/null || { \
echo ""; \
echo "ОШИБКА: Windows API заголовки не найдены."; \
echo "Установите пакет с заголовками MinGW-w64:"; \
echo " Arch: sudo pacman -S mingw-w64-headers"; \
echo " Debian: sudo apt install mingw-w64 # не gcc-mingw-w64-x86-64!"; \
echo " Fedora: sudo dnf install mingw64-headers"; \
echo ""; exit 1; }
@(command -v wget >/dev/null 2>&1 || command -v curl >/dev/null 2>&1) || { \
echo "ОШИБКА: требуется wget или curl"; exit 1; }
# ── FFTW: скачать исходники → собрать double → собрать float ──────────────────
fftw: fftw-double fftw-float
# Шаг 1: скачать tarball
third_party/$(FFTW_TAR):
mkdir -p third_party
@echo ">>> Скачиваем FFTW $(FFTW_VERSION)..."
@if command -v wget >/dev/null 2>&1; then \
wget -q --show-progress -O $@ "$(FFTW_URL)"; \
else \
curl -L --progress-bar -o $@ "$(FFTW_URL)"; \
fi
# Шаг 2: распаковать
$(FFTW_SRC)/configure: third_party/$(FFTW_TAR)
@echo ">>> Распаковываем FFTW..."
tar xf $< -C third_party/
@touch $@
# Шаг 3a: configure для double precision
$(FFTW_BUILD_D)/Makefile: $(FFTW_SRC)/configure
mkdir -p $(FFTW_BUILD_D)
@echo ">>> Конфигурируем FFTW (double)..."
cd $(FFTW_BUILD_D) && $(FFTW_SRC)/configure $(FFTW_CONF_COMMON)
# Шаг 4a: сборка и установка double precision
fftw-double: $(FFTW_LIB_D)
$(FFTW_LIB_D): $(FFTW_BUILD_D)/Makefile
@echo ">>> Собираем FFTW (double)..."
$(MAKE) -C $(FFTW_BUILD_D) install
# Шаг 3b: configure для float precision (--enable-float)
$(FFTW_BUILD_F)/Makefile: $(FFTW_SRC)/configure
mkdir -p $(FFTW_BUILD_F)
@echo ">>> Конфигурируем FFTW (float)..."
cd $(FFTW_BUILD_F) && $(FFTW_SRC)/configure $(FFTW_CONF_COMMON) --enable-float
# Шаг 4b: сборка и установка float precision
# Зависит от fftw-double чтобы install-шаги не конкурировали за prefix
fftw-float: $(FFTW_LIB_F)
$(FFTW_LIB_F): $(FFTW_BUILD_F)/Makefile $(FFTW_LIB_D)
@echo ">>> Собираем FFTW (float)..."
$(MAKE) -C $(FFTW_BUILD_F) install
# ── Third-party библиотеки (кросс-компиляция) ─────────────────────────────────
third-party: $(NR34_DEPS)
$(RNNOISE_LIB): $(RNNOISE_OBJS) | $(OUTDIR)
$(AR) rv $@ $^
$(RANLIB) $@
$(SPECBLEACH_LIB): $(SPECBLEACH_OBJS) | $(OUTDIR)
$(AR) rv $@ $^
$(RANLIB) $@
# % в GNU make совпадает через '/', поэтому паттерн покрывает вложенные пути
$(OBJDIR)/rnnoise/%.o: $(RNNOISE_DIR)/%.c
@mkdir -p $(@D)
$(CC) $(CFLAGS) -I$(RNNOISE_DIR)/include -I$(RNNOISE_DIR)/src -c -o $@ $<
$(OBJDIR)/specbleach/%.o: $(SPECBLEACH_DIR)/%.c | $(FFTW_LIB_D)
@mkdir -p $(@D)
$(CC) $(CFLAGS) -I$(FFTW_INST)/include \
-I$(SPECBLEACH_DIR)/include -I$(SPECBLEACH_DIR)/src -I$(SPECBLEACH_DIR)/src/shared \
-c -o $@ $<
# ── Основная библиотека ───────────────────────────────────────────────────────
$(OUTDIR) $(OBJDIR):
mkdir -p $@
$(DLL): $(OBJS) $(NR34_DEPS) $(FFTW_LIB_D) $(FFTW_LIB_F) | $(OUTDIR)
@echo ">>> Линкуем $@..."
$(CC) -shared \
-Wl,--out-implib,$(IMPLIB) \
$(LDFLAGS) \
-o $@ \
$(OBJS) $(LINK_LIBS)
@cp $(FFTW_DLL_D) $(FFTW_DLL_F) $(OUTDIR)/
@echo ""
@echo "=== Готово ==="
@echo " DLL: $(DLL)"
@echo " Import lib: $(IMPLIB)"
@echo " Заголовок: wdsp.h"
@echo ""
@echo " FFTW DLL скопированы в $(OUTDIR)/ — включите их в дистрибутив"
@echo " рядом с libwdsp.dll."
$(STATIC_LIB): $(OBJS) | $(OUTDIR)
$(AR) rv $@ $(OBJS)
$(RANLIB) $@
@echo ""
@echo "=== Готово ==="
@echo " Статическая: $(STATIC_LIB)"
@echo " Линкуйте приложение с:"
@echo " $(STATIC_LIB) $(NR34_DEPS)"
@echo " -L$(FFTW_INST)/lib -lfftw3 -lfftw3f -lavrt -lm"
$(OBJDIR)/%.o: %.c | $(OBJDIR) $(FFTW_LIB_D)
$(COMPILE) -c -o $@ $<
# ── Очистка ───────────────────────────────────────────────────────────────────
clean:
-rm -rf $(OBJDIR) $(OUTDIR)
# distclean удаляет собранный и скачанный FFTW — при следующем запуске пересоберётся
distclean: clean
-rm -rf $(FFTW_INST) $(FFTW_SRC) third_party/$(FFTW_TAR)
+91 -12
View File
@@ -13,14 +13,18 @@ wdsp/
├── org_openhpsdr_dsp_Wdsp.c/.h — JNI bridge ├── org_openhpsdr_dsp_Wdsp.c/.h — JNI bridge
├── Makefile — host build (Linux / macOS) ├── Makefile — host build (Linux / macOS)
├── Makefile.android — Android cross-build ├── Makefile.android — Android cross-build
├── Makefile.windows — Windows cross-build (MinGW-w64, runs on Linux)
├── java/ ├── java/
│ └── org/openhpsdr/dsp/Wdsp.java │ └── org/openhpsdr/dsp/Wdsp.java
├── third_party/ ├── third_party/
│ ├── fftw/ — FFTW source (download manually, see below) │ ├── fftw-3.3.11/ — FFTW source (downloaded automatically)
│ ├── fftw-win64/ — FFTW Windows binaries (downloaded automatically)
│ ├── rnnoise/ — RNNoise noise suppression │ ├── rnnoise/ — RNNoise noise suppression
│ └── libspecbleach/ — spectral noise reduction │ └── libspecbleach/ — spectral noise reduction
├── obj/ — object files (generated) ├── obj/ — object files (generated)
── lib/ — built libraries (generated) ── obj_win/ — Windows object files (generated)
├── lib/ — built libraries (generated)
└── lib_win/ — Windows libraries (generated)
``` ```
--- ---
@@ -81,20 +85,95 @@ make clean
--- ---
## Windows build (кросс-компиляция с Linux)
Сборка выполняется на Linux с помощью MinGW-w64. FFTW скачивается и собирается из исходников автоматически.
### Зависимости
```bash
# Arch
sudo pacman -S mingw-w64-gcc # тянет mingw-w64-headers автоматически
# Debian / Ubuntu — нужен мета-пакет mingw-w64, а не только компилятор
sudo apt install mingw-w64
# Fedora
sudo dnf install mingw64-gcc # тянет mingw64-headers автоматически
```
Также нужен `wget` или `curl`.
> **Debian/Ubuntu:** пакет `gcc-mingw-w64-x86-64` содержит только компилятор без Windows API заголовков — `Windows.h` не будет найден. Используйте `mingw-w64`.
### Сборка
```bash
# DLL + статическая библиотека (рекомендуется)
make -f Makefile.windows
# Только DLL
make -f Makefile.windows dll
# Только статическая библиотека
make -f Makefile.windows static
```
Результат в `lib_win/`:
| Файл | Назначение |
|---|---|
| `libwdsp.dll` | DLL для Windows |
| `libwdsp.dll.a` | Import library для MinGW (`-lwdsp`) |
| `libwdsp.a` | Статическая библиотека |
| `libfftw3-3.dll` | Runtime dependency — распространять вместе с DLL |
| `libfftw3f-3.dll` | Runtime dependency — распространять вместе с DLL |
### Линковка приложения
**MinGW:**
```bash
gcc myapp.c -Llib_win -lwdsp -o myapp.exe
```
**MSVC** — переименовать `libwdsp.dll.a` в `libwdsp.lib` и подключить обычным образом.
**Статически** (MinGW):
```bash
gcc myapp.c libwdsp.a third_party/rnnoise/librnnoise.a \
third_party/libspecbleach/libspecbleach.a \
-Lthird_party/fftw-win64 -lfftw3-3 -lfftw3f-3 -lavrt -lm -o myapp.exe
```
### Параметры
| Переменная | По умолчанию | Описание |
|---|---|---|
| `MINGW_PREFIX` | `x86_64-w64-mingw32` | Префикс MinGW toolchain |
| `FFTW_VERSION` | `3.3.11` | Версия FFTW |
| `CFLAGS` | `-O3 -Wno-parentheses` | Флаги компилятора |
```bash
make -f Makefile.windows MINGW_PREFIX=i686-w64-mingw32 # 32-bit Windows
```
### Очистка
```bash
make -f Makefile.windows clean # артефакты сборки
make -f Makefile.windows distclean # + удалить скачанный FFTW
```
---
## Android build ## Android build
### Prerequisites ### Prerequisites
1. **Android NDK** r23 or newer 1. **Android NDK** r23 or newer
2. **FFTW source** — download and extract into `third_party/fftw/`: 2. **Java compiler** — for building the `.class` file (Android Studio's JBR or any JDK)
```bash FFTW 3.3.11 скачивается и собирается автоматически. Чтобы использовать уже скачанный исходник, передайте `FFTW_SRC=/path/to/fftw-3.3.11`.
wget https://www.fftw.org/fftw-3.3.10.tar.gz
tar xf fftw-3.3.10.tar.gz
mv fftw-3.3.10 third_party/fftw
```
3. **Java compiler** — for building the `.class` file (Android Studio's JBR or any JDK)
### Build ### Build
@@ -144,8 +223,8 @@ make -f Makefile.android \
### Clean ### Clean
```bash ```bash
make -f Makefile.android clean make -f Makefile.android clean # удаляет obj/android/ и lib/android/
# removes obj/android/ and lib/android/ make -f Makefile.android distclean # + удаляет скачанный FFTW
``` ```
--- ---
+32
View File
@@ -233,6 +233,23 @@ void create_rxa (int channel)
max(2048, ch[channel].dsp_size), // number of coefficients for noise filter max(2048, ch[channel].dsp_size), // number of coefficients for noise filter
0); // minimum phase flag 0); // minimum phase flag
// WFM demod
rxa[channel].wfmd.p = create_wfmd (
0, // run
ch[channel].dsp_size, // buffer size
rxa[channel].midbuff, // pointer to input buffer
rxa[channel].midbuff, // pointer to output buffer
ch[channel].dsp_rate, // sample rate
75000.0, // deviation
20.0, // f_low
15000.0, // f_high
0.02, // tau - for dc removal
1, // run de-emphasis
75.0e-6, // de-emphasis time constant
0.5, // audio gain
max(2048, ch[channel].dsp_size), // # coefs for audio cutoff filter
0); // min phase flag for audio cutoff filter
// snba // snba
rxa[channel].snba.p = create_snba ( rxa[channel].snba.p = create_snba (
0, // run 0, // run
@@ -579,6 +596,7 @@ void destroy_rxa (int channel)
destroy_anf (rxa[channel].anf.p); destroy_anf (rxa[channel].anf.p);
destroy_eqp (rxa[channel].eqp.p); destroy_eqp (rxa[channel].eqp.p);
destroy_snba (rxa[channel].snba.p); destroy_snba (rxa[channel].snba.p);
destroy_wfmd (rxa[channel].wfmd.p);
destroy_fmsq (rxa[channel].fmsq.p); destroy_fmsq (rxa[channel].fmsq.p);
destroy_fmd (rxa[channel].fmd.p); destroy_fmd (rxa[channel].fmd.p);
destroy_amd (rxa[channel].amd.p); destroy_amd (rxa[channel].amd.p);
@@ -614,6 +632,7 @@ void flush_rxa (int channel)
flush_amd (rxa[channel].amd.p); flush_amd (rxa[channel].amd.p);
flush_fmd (rxa[channel].fmd.p); flush_fmd (rxa[channel].fmd.p);
flush_fmsq (rxa[channel].fmsq.p); flush_fmsq (rxa[channel].fmsq.p);
flush_wfmd (rxa[channel].wfmd.p);
flush_snba (rxa[channel].snba.p); flush_snba (rxa[channel].snba.p);
flush_eqp (rxa[channel].eqp.p); flush_eqp (rxa[channel].eqp.p);
flush_anf (rxa[channel].anf.p); flush_anf (rxa[channel].anf.p);
@@ -649,6 +668,7 @@ void xrxa (int channel)
xamd (rxa[channel].amd.p); xamd (rxa[channel].amd.p);
xfmd (rxa[channel].fmd.p); xfmd (rxa[channel].fmd.p);
xfmsq (rxa[channel].fmsq.p); xfmsq (rxa[channel].fmsq.p);
xwfmd (rxa[channel].wfmd.p);
xbpsnbain (rxa[channel].bpsnba.p, 1); xbpsnbain (rxa[channel].bpsnba.p, 1);
xbpsnbaout (rxa[channel].bpsnba.p, 1); xbpsnbaout (rxa[channel].bpsnba.p, 1);
xsnba (rxa[channel].snba.p); xsnba (rxa[channel].snba.p);
@@ -733,6 +753,7 @@ void setDSPSamplerate_rxa (int channel)
setSamplerate_fmd (rxa[channel].fmd.p, ch[channel].dsp_rate); setSamplerate_fmd (rxa[channel].fmd.p, ch[channel].dsp_rate);
setBuffers_fmsq (rxa[channel].fmsq.p, rxa[channel].midbuff, rxa[channel].midbuff, rxa[channel].fmd.p->audio); setBuffers_fmsq (rxa[channel].fmsq.p, rxa[channel].midbuff, rxa[channel].midbuff, rxa[channel].fmd.p->audio);
setSamplerate_fmsq (rxa[channel].fmsq.p, ch[channel].dsp_rate); setSamplerate_fmsq (rxa[channel].fmsq.p, ch[channel].dsp_rate);
setSamplerate_wfmd (rxa[channel].wfmd.p, ch[channel].dsp_rate);
setSamplerate_snba (rxa[channel].snba.p, ch[channel].dsp_rate); setSamplerate_snba (rxa[channel].snba.p, ch[channel].dsp_rate);
setSamplerate_eqp (rxa[channel].eqp.p, ch[channel].dsp_rate); setSamplerate_eqp (rxa[channel].eqp.p, ch[channel].dsp_rate);
setSamplerate_anf (rxa[channel].anf.p, ch[channel].dsp_rate); setSamplerate_anf (rxa[channel].anf.p, ch[channel].dsp_rate);
@@ -794,6 +815,8 @@ void setDSPBuffsize_rxa (int channel)
setSize_fmd (rxa[channel].fmd.p, ch[channel].dsp_size); setSize_fmd (rxa[channel].fmd.p, ch[channel].dsp_size);
setBuffers_fmsq (rxa[channel].fmsq.p, rxa[channel].midbuff, rxa[channel].midbuff, rxa[channel].fmd.p->audio); setBuffers_fmsq (rxa[channel].fmsq.p, rxa[channel].midbuff, rxa[channel].midbuff, rxa[channel].fmd.p->audio);
setSize_fmsq (rxa[channel].fmsq.p, ch[channel].dsp_size); setSize_fmsq (rxa[channel].fmsq.p, ch[channel].dsp_size);
setBuffers_wfmd (rxa[channel].wfmd.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_wfmd (rxa[channel].wfmd.p, ch[channel].dsp_size);
setBuffers_snba (rxa[channel].snba.p, rxa[channel].midbuff, rxa[channel].midbuff); setBuffers_snba (rxa[channel].snba.p, rxa[channel].midbuff, rxa[channel].midbuff);
setSize_snba (rxa[channel].snba.p, ch[channel].dsp_size); setSize_snba (rxa[channel].snba.p, ch[channel].dsp_size);
setBuffers_eqp (rxa[channel].eqp.p, rxa[channel].midbuff, rxa[channel].midbuff); setBuffers_eqp (rxa[channel].eqp.p, rxa[channel].midbuff, rxa[channel].midbuff);
@@ -857,6 +880,7 @@ void SetRXAMode (int channel, int mode)
rxa[channel].mode = mode; rxa[channel].mode = mode;
rxa[channel].amd.p->run = 0; rxa[channel].amd.p->run = 0;
rxa[channel].fmd.p->run = 0; rxa[channel].fmd.p->run = 0;
rxa[channel].wfmd.p->run = 0;
rxa[channel].agc.p->run = 1; rxa[channel].agc.p->run = 1;
switch (mode) switch (mode)
{ {
@@ -875,6 +899,10 @@ void SetRXAMode (int channel, int mode)
rxa[channel].fmd.p->run = 1; rxa[channel].fmd.p->run = 1;
rxa[channel].agc.p->run = 0; rxa[channel].agc.p->run = 0;
break; break;
case RXA_WFM:
rxa[channel].wfmd.p->run = 1;
rxa[channel].agc.p->run = 0;
break;
default: default:
break; break;
@@ -961,6 +989,7 @@ void RXAbpsnbaCheck (int channel, int mode, int notch_run)
run_notches = 0; run_notches = 0;
break; break;
case RXA_FM: case RXA_FM:
case RXA_WFM:
f_low = +a->abs_low_freq; f_low = +a->abs_low_freq;
f_high = +a->abs_high_freq; f_high = +a->abs_high_freq;
run_notches = 0; run_notches = 0;
@@ -1010,6 +1039,7 @@ void RXAbpsnbaSet (int channel)
a->position = 1; a->position = 1;
break; break;
case RXA_FM: case RXA_FM:
case RXA_WFM:
a->run = rxa[channel].snba.p->run; a->run = rxa[channel].snba.p->run;
a->position = 1; a->position = 1;
break; break;
@@ -1046,6 +1076,7 @@ void RXASetNC (int channel, int nc)
SetRXAFMSQNC (channel, nc); SetRXAFMSQNC (channel, nc);
SetRXAFMNCde (channel, nc); SetRXAFMNCde (channel, nc);
SetRXAFMNCaud (channel, nc); SetRXAFMNCaud (channel, nc);
SetRXAWFMNCaud (channel, nc);
SetChannelState (channel, oldstate, 0); SetChannelState (channel, oldstate, 0);
} }
@@ -1059,4 +1090,5 @@ void RXASetMP (int channel, int mp)
SetRXAFMSQMP (channel, mp); SetRXAFMSQMP (channel, mp);
SetRXAFMMPde (channel, mp); SetRXAFMMPde (channel, mp);
SetRXAFMMPaud (channel, mp); SetRXAFMMPaud (channel, mp);
SetRXAWFMMPaud (channel, mp);
} }
+6 -1
View File
@@ -41,7 +41,8 @@ enum rxaMode
RXA_SPEC, RXA_SPEC,
RXA_DIGL, RXA_DIGL,
RXA_SAM, RXA_SAM,
RXA_DRM RXA_DRM,
RXA_WFM
}; };
enum rxaMeterType enum rxaMeterType
@@ -121,6 +122,10 @@ struct _rxa
FMSQ p; FMSQ p;
} fmsq; } fmsq;
struct struct
{
WFMD p;
} wfmd;
struct
{ {
EQP p; EQP p;
} eqp; } eqp;
+30
View File
@@ -358,6 +358,21 @@ void create_txa (int channel)
max(2048, ch[channel].dsp_size), // number coefficients for bandpass filter max(2048, ch[channel].dsp_size), // number coefficients for bandpass filter
0); // minimum phase flag 0); // minimum phase flag
txa[channel].wfmmod.p = create_wfmmod (
0, // run - OFF by default
ch[channel].dsp_size, // size
txa[channel].midbuff, // pointer to input buffer
txa[channel].midbuff, // pointer to output buffer
ch[channel].dsp_rate, // samplerate
75000.0, // deviation
20.0, // low cutoff frequency
15000.0, // high cutoff frequency
1, // run pre-emphasis
75.0e-6, // pre-emphasis time constant
1, // run bandpass filter
max(2048, ch[channel].dsp_size), // number coefficients for bandpass filter
0); // minimum phase flag
txa[channel].gen1.p = create_gen ( txa[channel].gen1.p = create_gen (
0, // run 0, // run
ch[channel].dsp_size, // buffer size ch[channel].dsp_size, // buffer size
@@ -490,6 +505,7 @@ void destroy_txa (int channel)
destroy_meter (txa[channel].alcmeter.p); destroy_meter (txa[channel].alcmeter.p);
destroy_uslew (txa[channel].uslew.p); destroy_uslew (txa[channel].uslew.p);
destroy_gen (txa[channel].gen1.p); destroy_gen (txa[channel].gen1.p);
destroy_wfmmod (txa[channel].wfmmod.p);
destroy_fmmod (txa[channel].fmmod.p); destroy_fmmod (txa[channel].fmmod.p);
destroy_ammod (txa[channel].ammod.p); destroy_ammod (txa[channel].ammod.p);
destroy_wcpagc (txa[channel].alc.p); destroy_wcpagc (txa[channel].alc.p);
@@ -544,6 +560,7 @@ void flush_txa (int channel)
flush_wcpagc (txa[channel].alc.p); flush_wcpagc (txa[channel].alc.p);
flush_ammod (txa[channel].ammod.p); flush_ammod (txa[channel].ammod.p);
flush_fmmod (txa[channel].fmmod.p); flush_fmmod (txa[channel].fmmod.p);
flush_wfmmod (txa[channel].wfmmod.p);
flush_gen (txa[channel].gen1.p); flush_gen (txa[channel].gen1.p);
flush_uslew (txa[channel].uslew.p); flush_uslew (txa[channel].uslew.p);
flush_meter (txa[channel].alcmeter.p); flush_meter (txa[channel].alcmeter.p);
@@ -580,6 +597,7 @@ void xtxa (int channel)
xammod (txa[channel].ammod.p); // AM Modulator xammod (txa[channel].ammod.p); // AM Modulator
xemphp (txa[channel].preemph.p, 1); // FM pre-emphasis (second option) xemphp (txa[channel].preemph.p, 1); // FM pre-emphasis (second option)
xfmmod (txa[channel].fmmod.p); // FM Modulator xfmmod (txa[channel].fmmod.p); // FM Modulator
xwfmmod (txa[channel].wfmmod.p); // WFM Modulator (pre-emphasis is internal)
xgen (txa[channel].gen1.p); // output signal generator (TUN and Two-tone) xgen (txa[channel].gen1.p); // output signal generator (TUN and Two-tone)
xuslew (txa[channel].uslew.p); // up-slew for AM, FM, and gens xuslew (txa[channel].uslew.p); // up-slew for AM, FM, and gens
xmeter (txa[channel].alcmeter.p); // ALC Meter xmeter (txa[channel].alcmeter.p); // ALC Meter
@@ -653,6 +671,7 @@ void setDSPSamplerate_txa (int channel)
setSamplerate_wcpagc (txa[channel].alc.p, ch[channel].dsp_rate); setSamplerate_wcpagc (txa[channel].alc.p, ch[channel].dsp_rate);
setSamplerate_ammod (txa[channel].ammod.p, ch[channel].dsp_rate); setSamplerate_ammod (txa[channel].ammod.p, ch[channel].dsp_rate);
setSamplerate_fmmod (txa[channel].fmmod.p, ch[channel].dsp_rate); setSamplerate_fmmod (txa[channel].fmmod.p, ch[channel].dsp_rate);
setSamplerate_wfmmod (txa[channel].wfmmod.p, ch[channel].dsp_rate);
setSamplerate_gen (txa[channel].gen1.p, ch[channel].dsp_rate); setSamplerate_gen (txa[channel].gen1.p, ch[channel].dsp_rate);
setSamplerate_uslew (txa[channel].uslew.p, ch[channel].dsp_rate); setSamplerate_uslew (txa[channel].uslew.p, ch[channel].dsp_rate);
setSamplerate_meter (txa[channel].alcmeter.p, ch[channel].dsp_rate); setSamplerate_meter (txa[channel].alcmeter.p, ch[channel].dsp_rate);
@@ -723,6 +742,8 @@ void setDSPBuffsize_txa (int channel)
setSize_ammod (txa[channel].ammod.p, ch[channel].dsp_size); setSize_ammod (txa[channel].ammod.p, ch[channel].dsp_size);
setBuffers_fmmod (txa[channel].fmmod.p, txa[channel].midbuff, txa[channel].midbuff); setBuffers_fmmod (txa[channel].fmmod.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_fmmod (txa[channel].fmmod.p, ch[channel].dsp_size); setSize_fmmod (txa[channel].fmmod.p, ch[channel].dsp_size);
setBuffers_wfmmod (txa[channel].wfmmod.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_wfmmod (txa[channel].wfmmod.p, ch[channel].dsp_size);
setBuffers_gen (txa[channel].gen1.p, txa[channel].midbuff, txa[channel].midbuff); setBuffers_gen (txa[channel].gen1.p, txa[channel].midbuff, txa[channel].midbuff);
setSize_gen (txa[channel].gen1.p, ch[channel].dsp_size); setSize_gen (txa[channel].gen1.p, ch[channel].dsp_size);
setBuffers_uslew (txa[channel].uslew.p, txa[channel].midbuff, txa[channel].midbuff); setBuffers_uslew (txa[channel].uslew.p, txa[channel].midbuff, txa[channel].midbuff);
@@ -758,6 +779,7 @@ void SetTXAMode (int channel, int mode)
txa[channel].mode = mode; txa[channel].mode = mode;
txa[channel].ammod.p->run = 0; txa[channel].ammod.p->run = 0;
txa[channel].fmmod.p->run = 0; txa[channel].fmmod.p->run = 0;
txa[channel].wfmmod.p->run = 0;
txa[channel].preemph.p->run = 0; txa[channel].preemph.p->run = 0;
switch (mode) switch (mode)
{ {
@@ -779,6 +801,10 @@ void SetTXAMode (int channel, int mode)
txa[channel].fmmod.p->run = 1; txa[channel].fmmod.p->run = 1;
txa[channel].preemph.p->run = 1; txa[channel].preemph.p->run = 1;
break; break;
case TXA_WFM:
// wfmmod carries its own RC pre-emphasis; the shared emphp stays off
txa[channel].wfmmod.p->run = 1;
break;
default: default:
break; break;
@@ -820,6 +846,7 @@ int TXAUslewCheck (int channel)
{ {
return (txa[channel].ammod.p->run == 1) || return (txa[channel].ammod.p->run == 1) ||
(txa[channel].fmmod.p->run == 1) || (txa[channel].fmmod.p->run == 1) ||
(txa[channel].wfmmod.p->run == 1) ||
(txa[channel].gen0.p->run == 1) || (txa[channel].gen0.p->run == 1) ||
(txa[channel].gen1.p->run == 1); (txa[channel].gen1.p->run == 1);
} }
@@ -855,6 +882,7 @@ void TXASetupBPFilters (int channel)
case TXA_AM: case TXA_AM:
case TXA_SAM: case TXA_SAM:
case TXA_FM: case TXA_FM:
case TXA_WFM:
if (txa[channel].compressor.p->run) if (txa[channel].compressor.p->run)
{ {
CalcBandpassFilter (txa[channel].bp0.p, 0.0, txa[channel].f_high, 2.0); CalcBandpassFilter (txa[channel].bp0.p, 0.0, txa[channel].f_high, 2.0);
@@ -914,6 +942,7 @@ void TXASetNC (int channel, int nc)
SetTXAFMEmphNC (channel, nc); SetTXAFMEmphNC (channel, nc);
SetTXAEQNC (channel, nc); SetTXAEQNC (channel, nc);
SetTXAFMNC (channel, nc); SetTXAFMNC (channel, nc);
SetTXAWFMNC (channel, nc);
SetTXACFIRNC (channel, nc); SetTXACFIRNC (channel, nc);
SetChannelState (channel, oldstate, 0); SetChannelState (channel, oldstate, 0);
} }
@@ -925,6 +954,7 @@ void TXASetMP (int channel, int mp)
SetTXAFMEmphMP (channel, mp); SetTXAFMEmphMP (channel, mp);
SetTXAEQMP (channel, mp); SetTXAEQMP (channel, mp);
SetTXAFMMP (channel, mp); SetTXAFMMP (channel, mp);
SetTXAWFMMP (channel, mp);
} }
PORT PORT
+6 -1
View File
@@ -43,7 +43,8 @@ enum txaMode
TXA_SAM, TXA_SAM,
TXA_DRM, TXA_DRM,
TXA_AM_LSB, TXA_AM_LSB,
TXA_AM_USB TXA_AM_USB,
TXA_WFM
}; };
enum txaMeterType enum txaMeterType
@@ -135,6 +136,10 @@ struct _txa
FMMOD p; FMMOD p;
} fmmod; } fmmod;
struct struct
{
WFMMOD p;
} wfmmod;
struct
{ {
SIPHON p; SIPHON p;
} sip1; } sip1;
+171 -2
View File
@@ -138,6 +138,7 @@ CALCC create_calcc (int channel, int runcal, int size, int rate, int ints, int s
a->stbl = stbl; a->stbl = stbl;
a->npsamps = npsamps; a->npsamps = npsamps;
a->alpha = alpha; a->alpha = alpha;
a->outlier_sigma = 0.0;
a->info = (int *) malloc0 (16 * sizeof (int)); a->info = (int *) malloc0 (16 * sizeof (int));
a->binfo = (int *) malloc0 (16 * sizeof (int)); a->binfo = (int *) malloc0 (16 * sizeof (int));
@@ -321,6 +322,125 @@ void rxscheck (int rints, double* tvec, double* coef, int* info)
if (out < 0.00) *info |= 0x0020; if (out < 0.00) *info |= 0x0020;
} }
// Yurij_eu2av: fallback rx_scale estimator. It averages the top few
// amplitude intervals (ignoring overrange samples) and linearly extrapolates
// to full TX scale (env_TX = 1/hw_scale). Used only if the cubic xbuilder
// fit fails or is rejected by rxscheck.
static int estimate_rx_scale_from_top_intervals(CALCC a, double* rx_scale_out)
{
const int n_top = 4;
double sx[4], sy[4], sw[4];
int valid = 0;
int b, j;
for (b = a->ints - 1; b >= 0 && valid < n_top; b--)
{
int base = b * a->spi;
double sum_x = 0.0, sum_y = 0.0;
int n = 0;
for (j = 0; j < a->spi; j++)
{
int k = base + j;
double nx = a->env_TX[k] * a->hw_scale;
if (nx > 1.0 || nx < 0.0) continue;
if (a->env_TX[k] < 1.0e-30 || a->env_RX[k] < 1.0e-30) continue;
sum_x += a->env_TX[k];
sum_y += a->env_RX[k];
n++;
}
if (n == 0) continue;
sx[valid] = sum_x / (double)n;
sy[valid] = sum_y / (double)n;
sw[valid] = (double)n;
valid++;
}
if (valid < 2) return -1;
{
double s_w = 0.0, s_x = 0.0, s_y = 0.0, s_xx = 0.0, s_xy = 0.0;
double det, aa, bb, target_x, y_at_target;
int i;
for (i = 0; i < valid; i++)
{
double w = sw[i];
s_w += w;
s_x += w * sx[i];
s_y += w * sy[i];
s_xx += w * sx[i] * sx[i];
s_xy += w * sx[i] * sy[i];
}
det = s_w * s_xx - s_x * s_x;
if (fabs(det) < 1e-30) return -1;
bb = (s_w * s_xy - s_x * s_y) / det;
aa = (s_y - bb * s_x) / s_w;
target_x = 1.0 / a->hw_scale;
y_at_target = aa + bb * target_x;
if (y_at_target <= 1e-15) return -1;
*rx_scale_out = 1.0 / y_at_target;
}
return 0;
}
// Yurij_eu2av: robust outlier rejection for the cubic-spline xbuilder.
// Fits rx = k*tx through the origin via median ratio, then rejects points
// whose residual exceeds sigma * MAD.
static int cmp_double(const void* a, const void* b)
{
double da = *(const double*)a;
double db = *(const double*)b;
if (da < db) return -1;
if (da > db) return 1;
return 0;
}
static double median_double(double* v, int n)
{
if (n <= 0) return 0.0;
if (n % 2 == 1)
return v[n / 2];
else
return 0.5 * (v[n / 2 - 1] + v[n / 2]);
}
static int reject_outliers(double* tx, double* rx, int n, double sigma)
{
const int min_points = 32;
int i, keep = 0;
double* ratios;
double* absres;
double med_ratio, med_absres, thr;
if (n < min_points || sigma <= 0.0) return n;
ratios = (double*)malloc0(n * sizeof(double));
for (i = 0; i < n; i++)
ratios[i] = (tx[i] > 1.0e-30) ? rx[i] / tx[i] : 0.0;
qsort(ratios, n, sizeof(double), cmp_double);
med_ratio = median_double(ratios, n);
_aligned_free(ratios);
if (fabs(med_ratio) < 1.0e-30) return n;
absres = (double*)malloc0(n * sizeof(double));
for (i = 0; i < n; i++)
absres[i] = fabs(rx[i] - med_ratio * tx[i]);
qsort(absres, n, sizeof(double), cmp_double);
med_absres = median_double(absres, n);
_aligned_free(absres);
if (med_absres < 1.0e-30) return n;
thr = sigma * med_absres;
for (i = 0; i < n; i++)
{
if (fabs(rx[i] - med_ratio * tx[i]) <= thr)
{
tx[keep] = tx[i];
rx[keep] = rx[i];
keep++;
}
}
return (keep >= min_points) ? keep : n;
}
void calc (CALCC a) void calc (CALCC a)
{ {
int i; int i;
@@ -336,21 +456,60 @@ void calc (CALCC a)
double tvec[3]; double tvec[3];
double txrxcoefs[4 * 2]; double txrxcoefs[4 * 2];
double rx_scale; double rx_scale;
int xb_ok = 0;
double* tx_filt;
double* rx_filt;
int n_filt = 0;
if (a->ints < 16) rints = 1; if (a->ints < 16) rints = 1;
else rints = 2; else rints = 2;
ix = rints - 1; ix = rints - 1;
for (i = 0; i <= rints; i++) for (i = 0; i <= rints; i++)
tvec[i] = (double)i / (double)rints / a->hw_scale; tvec[i] = (double)i / (double)rints / a->hw_scale;
dx = tvec[rints] - tvec[rints - 1]; dx = tvec[rints] - tvec[rints - 1];
xbuilder(a->ccbld, a->nsamps, a->env_TX, a->env_RX, rints, tvec, &(a->binfo[0]), txrxcoefs, a->ptol);
// Yurij_eu2av: build a filtered dataset with overrange samples removed
// before running xbuilder. Overrange env_TX*hw_scale > 1.0 can distort
// the cubic fit and produce an incorrect rx_scale.
tx_filt = (double*)malloc0(a->nsamps * sizeof(double));
rx_filt = (double*)malloc0(a->nsamps * sizeof(double));
for (i = 0; i < a->nsamps; i++)
{
double nx = a->env_TX[i] * a->hw_scale;
if (nx > 1.0 || nx < 0.0) continue;
if (a->env_TX[i] < 1.0e-30 || a->env_RX[i] < 1.0e-30) continue;
tx_filt[n_filt] = a->env_TX[i];
rx_filt[n_filt] = a->env_RX[i];
n_filt++;
}
// Yurij_eu2av: optional outlier rejection before cubic-spline fit.
if (a->outlier_sigma > 0.0)
n_filt = reject_outliers(tx_filt, rx_filt, n_filt, a->outlier_sigma);
xbuilder(a->ccbld, n_filt, tx_filt, rx_filt, rints, tvec, &(a->binfo[0]), txrxcoefs, a->ptol);
rxscheck (rints, tvec, txrxcoefs, &a->binfo[7]); rxscheck (rints, tvec, txrxcoefs, &a->binfo[7]);
if ((a->binfo[0] == 0) && (a->binfo[7] == 0)) if ((a->binfo[0] == 0) && (a->binfo[7] == 0))
{
rx_scale = 1.0 / (txrxcoefs[4 * ix + 0] + dx * (txrxcoefs[4 * ix + 1] + dx * (txrxcoefs[4 * ix + 2] + dx * txrxcoefs[4 * ix + 3]))); rx_scale = 1.0 / (txrxcoefs[4 * ix + 0] + dx * (txrxcoefs[4 * ix + 1] + dx * (txrxcoefs[4 * ix + 2] + dx * txrxcoefs[4 * ix + 3])));
else xb_ok = 1;
}
else if (estimate_rx_scale_from_top_intervals(a, &rx_scale) == 0)
{
// Yurij_eu2av: xbuilder failed, but the bucket-average fallback
// gave a usable rx_scale. Keep binfo[0] bit 0 set for diagnostics.
a->binfo[0] |= 0x0001;
xb_ok = 1;
}
_aligned_free(tx_filt);
_aligned_free(rx_filt);
if (!xb_ok)
{ {
a->scOK = 0; a->scOK = 0;
goto cleanup; goto cleanup;
} }
if (a->stbl && _InterlockedAnd (&a->ctrl.running, 1)) if (a->stbl && _InterlockedAnd (&a->ctrl.running, 1))
a->rx_scale = a->alpha * a->rx_scale + (1.0 - a->alpha) * rx_scale; a->rx_scale = a->alpha * a->rx_scale + (1.0 - a->alpha) * rx_scale;
else else
@@ -1046,6 +1205,16 @@ void SetPSPtol (int channel, double ptol)
LeaveCriticalSection (&txa[channel].calcc.cs_update); LeaveCriticalSection (&txa[channel].calcc.cs_update);
} }
PORT
void SetPSOutlierSigma (int channel, double sigma)
{
// Yurij_eu2av: 0.0 disables the pre-xbuilder outlier filter.
if (sigma < 0.0) sigma = 0.0;
EnterCriticalSection (&txa[channel].calcc.cs_update);
txa[channel].calcc.p->outlier_sigma = sigma;
LeaveCriticalSection (&txa[channel].calcc.cs_update);
}
PORT PORT
void GetPSDisp (int channel, double* x, double* ym, double* yc, double* ys, double* cm, double* cc, double* cs) void GetPSDisp (int channel, double* x, double* ym, double* yc, double* ys, double* cm, double* cc, double* cs)
{ {
+1
View File
@@ -48,6 +48,7 @@ typedef struct _calcc
double hw_scale; double hw_scale;
double rx_scale; double rx_scale;
double alpha; double alpha;
double outlier_sigma;
int tsamps; int tsamps;
double* env_TX; double* env_TX;
+8 -1
View File
@@ -33,7 +33,7 @@ warren@wpratt.com
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
#include <Windows.h> #include <windows.h>
#include <process.h> #include <process.h>
#include <intrin.h> #include <intrin.h>
#endif #endif
@@ -43,6 +43,11 @@ warren@wpratt.com
#ifdef _WIN32 #ifdef _WIN32
#include <avrt.h> #include <avrt.h>
#endif #endif
#ifndef WDSP_FPE_GUARD
#define WDSP_FPE_GUARD ((void)0)
#define WDSP_FPE_RESTORE ((void)0)
#endif
#include "fftw3.h" #include "fftw3.h"
#include "amd.h" #include "amd.h"
@@ -77,6 +82,8 @@ warren@wpratt.com
#include "fmd.h" #include "fmd.h"
#include "fmmod.h" #include "fmmod.h"
#include "fmsq.h" #include "fmsq.h"
#include "wfmd.h"
#include "wfmmod.h"
#include "gain.h" #include "gain.h"
#include "gaussian.h" #include "gaussian.h"
#include "gen.h" #include "gen.h"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+25
View File
@@ -277,6 +277,7 @@ extern void SetPSHWPeak (int channel, double peak);
extern void GetPSHWPeak (int channel, double* peak); extern void GetPSHWPeak (int channel, double* peak);
extern void GetPSMaxTX (int channel, double* maxtx); extern void GetPSMaxTX (int channel, double* maxtx);
extern void SetPSPtol (int channel, double ptol); extern void SetPSPtol (int channel, double ptol);
extern void SetPSOutlierSigma (int channel, double sigma);
extern void GetPSDisp (int channel, double* x, double* ym, double* yc, double* ys, double* cm, double* cc, double* cs); extern void GetPSDisp (int channel, double* x, double* ym, double* yc, double* ys, double* cm, double* cc, double* cs);
extern void SetPSFeedbackRate (int channel, int rate); extern void SetPSFeedbackRate (int channel, int rate);
extern void SetPSPinMode (int channel, int pin); extern void SetPSPinMode (int channel, int pin);
@@ -517,6 +518,30 @@ extern void SetTXAFMNC (int channel, int nc);
extern void SetTXAFMMP (int channel, int mp); extern void SetTXAFMMP (int channel, int mp);
extern void SetTXAFMAFFreqs (int channel, double low, double high); extern void SetTXAFMAFFreqs (int channel, double low, double high);
//
// Interfaces from wfmd.c
//
extern void SetRXAWFMDeviation (int channel, double deviation);
extern void SetRXAWFMNCaud (int channel, int nc);
extern void SetRXAWFMMPaud (int channel, int mp);
extern void SetRXAWFMAFFilter (int channel, double low, double high);
extern void SetRXAWFMDeemphRun (int channel, int run);
extern void SetRXAWFMDeemphTau (int channel, double tau);
extern void SetRXAWFMLimRun (int channel, int run);
extern void SetRXAWFMLimGain (int channel, double gaindB);
//
// Interfaces from wfmmod.c
//
extern void SetTXAWFMDeviation (int channel, double deviation);
extern void SetTXAWFMNC (int channel, int nc);
extern void SetTXAWFMMP (int channel, int mp);
extern void SetTXAWFMAFFreqs (int channel, double low, double high);
extern void SetTXAWFMPreEmphRun (int channel, int run);
extern void SetTXAWFMPreEmphTau (int channel, double tau);
// //
// Interfaces from fmsq.c // Interfaces from fmsq.c
// //
+322
View File
@@ -0,0 +1,322 @@
/* wfmd.c
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2013, 2023 Warren Pratt, NR0V
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#include "comm.h"
void calc_wfmd (WFMD a)
{
// discriminator
a->pre_i = 0.0;
a->pre_q = 0.0;
a->again = a->rate / (a->deviation * TWOPI);
// dc removal
a->mtau = exp(-1.0 / (a->rate * a->tau));
a->onem_mtau = 1.0 - a->mtau;
a->fmdc = 0.0;
// de-emphasis
if (a->tau_de > 0.0) a->mde = exp(-1.0 / (a->rate * a->tau_de));
else a->mde = 0.0;
a->onem_mde = 1.0 - a->mde;
a->deemph_z = 0.0;
// detector limiter
a->plim = create_wcpagc (
1, // run - always ON
5, // mode
1, // 0 for max(I,Q), 1 for envelope
a->out, // input buff pointer
a->out, // output buff pointer
a->size, // io_buffsize
(int)a->rate, // sample rate
0.001, // tau_attack
0.008, // tau_decay
4, // n_tau
a->lim_gain, // max_gain (sets threshold, initial value)
1.0, // var_gain / slope
1.0, // fixed_gain
1.0, // max_input
0.9, // out_targ
0.250, // tau_fast_backaverage
0.004, // tau_fast_decay
4.0, // pop_ratio
0, // hang_enable
0.500, // tau_hang_backmult
0.500, // hangtime
2.000, // hang_thresh
0.100); // tau_hang_decay
}
void decalc_wfmd (WFMD a)
{
destroy_wcpagc(a->plim);
}
WFMD create_wfmd (int run, int size, double* in, double* out, int rate, double deviation, double f_low, double f_high,
double tau, int deemph_run, double tau_de, double afgain, int nc_aud, int mp_aud)
{
WFMD a = (WFMD) malloc0 (sizeof (wfmd));
double* impulse;
a->run = run;
a->size = size;
a->in = in;
a->out = out;
a->rate = (double)rate;
a->deviation = deviation;
a->f_low = f_low;
a->f_high = f_high;
a->tau = tau;
a->deemph_run = deemph_run;
a->tau_de = tau_de;
a->afgain = afgain;
a->nc_aud = nc_aud;
a->mp_aud = mp_aud;
a->lim_run = 0;
a->lim_pre_gain = 0.4;
a->lim_gain = 2.5;
calc_wfmd (a);
a->audio = (double *) malloc0 (a->size * sizeof (complex));
// audio filter
impulse = fir_bandpass(a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size));
a->paud = create_fircore (a->size, a->audio, a->out, a->nc_aud, a->mp_aud, impulse);
_aligned_free (impulse);
return a;
}
void destroy_wfmd (WFMD a)
{
destroy_fircore (a->paud);
_aligned_free (a->audio);
decalc_wfmd (a);
_aligned_free (a);
}
void flush_wfmd (WFMD a)
{
memset (a->audio, 0, a->size * sizeof (complex));
flush_fircore (a->paud);
a->pre_i = 0.0;
a->pre_q = 0.0;
a->fmdc = 0.0;
a->deemph_z = 0.0;
flush_wcpagc (a->plim);
}
void xwfmd (WFMD a)
{
if (a->run)
{
int i;
double si, sq, cr, ci, det, aud;
for (i = 0; i < a->size; i++)
{
// quadrature discriminator: det = arg (x[n] * conj (x[n-1]))
si = a->in[2 * i + 0];
sq = a->in[2 * i + 1];
cr = + si * a->pre_i + sq * a->pre_q;
ci = - si * a->pre_q + sq * a->pre_i;
a->pre_i = si;
a->pre_q = sq;
det = atan2 (ci, cr);
// dc removal, gain, & demod output
a->fmdc = a->mtau * a->fmdc + a->onem_mtau * det;
aud = a->again * (det - a->fmdc);
// de-emphasis
if (a->deemph_run)
{
a->deemph_z = a->mde * a->deemph_z + a->onem_mde * aud;
aud = a->deemph_z;
}
a->audio[2 * i + 0] = aud;
a->audio[2 * i + 1] = aud;
}
// audio filter
xfircore (a->paud);
if (a->lim_run)
{
for (i = 0; i < 2 * a->size; i++)
a->out[i] *= a->lim_pre_gain;
xwcpagc (a->plim);
}
}
else if (a->in != a->out)
memcpy (a->out, a->in, a->size * sizeof (complex));
}
void setBuffers_wfmd (WFMD a, double* in, double* out)
{
decalc_wfmd (a);
a->in = in;
a->out = out;
calc_wfmd (a);
setBuffers_fircore (a->paud, a->audio, a->out);
setBuffers_wcpagc (a->plim, a->out, a->out);
}
void setSamplerate_wfmd (WFMD a, int rate)
{
double* impulse;
decalc_wfmd (a);
a->rate = rate;
calc_wfmd (a);
// audio filter
impulse = fir_bandpass(a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size));
setImpulse_fircore (a->paud, impulse, 1);
_aligned_free (impulse);
setSamplerate_wcpagc (a->plim, (int)a->rate);
}
void setSize_wfmd (WFMD a, int size)
{
double* impulse;
decalc_wfmd (a);
_aligned_free (a->audio);
a->size = size;
calc_wfmd (a);
a->audio = (double *) malloc0 (a->size * sizeof (complex));
// audio filter
destroy_fircore (a->paud);
impulse = fir_bandpass(a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size));
a->paud = create_fircore (a->size, a->audio, a->out, a->nc_aud, a->mp_aud, impulse);
_aligned_free (impulse);
setSize_wcpagc (a->plim, a->size);
}
/********************************************************************************************************
* *
* RXA Properties *
* *
********************************************************************************************************/
PORT
void SetRXAWFMDeviation (int channel, double deviation)
{
WFMD a;
EnterCriticalSection (&ch[channel].csDSP);
a = rxa[channel].wfmd.p;
a->deviation = deviation;
a->again = a->rate / (a->deviation * TWOPI);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT
void SetRXAWFMNCaud (int channel, int nc)
{
WFMD a;
double* impulse;
EnterCriticalSection (&ch[channel].csDSP);
a = rxa[channel].wfmd.p;
if (a->nc_aud != nc)
{
a->nc_aud = nc;
impulse = fir_bandpass(a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size));
setNc_fircore (a->paud, a->nc_aud, impulse);
_aligned_free (impulse);
}
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT
void SetRXAWFMMPaud (int channel, int mp)
{
WFMD a;
a = rxa[channel].wfmd.p;
if (a->mp_aud != mp)
{
a->mp_aud = mp;
setMp_fircore (a->paud, a->mp_aud);
}
}
PORT
void SetRXAWFMAFFilter (int channel, double low, double high)
{
WFMD a = rxa[channel].wfmd.p;
double* impulse;
EnterCriticalSection (&ch[channel].csDSP);
if (a->f_low != low || a->f_high != high)
{
a->f_low = low;
a->f_high = high;
impulse = fir_bandpass (a->nc_aud, 0.8 * a->f_low, 1.1 * a->f_high, a->rate, 0, 1, a->afgain / (2.0 * a->size));
setImpulse_fircore (a->paud, impulse, 1);
_aligned_free (impulse);
}
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT
void SetRXAWFMDeemphRun (int channel, int run)
{
WFMD a = rxa[channel].wfmd.p;
EnterCriticalSection (&ch[channel].csDSP);
if (a->deemph_run != run)
{
a->deemph_run = run;
a->deemph_z = 0.0;
}
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT
void SetRXAWFMDeemphTau (int channel, double tau)
{
WFMD a = rxa[channel].wfmd.p;
EnterCriticalSection (&ch[channel].csDSP);
if (a->tau_de != tau && tau > 0.0)
{
a->tau_de = tau;
a->mde = exp(-1.0 / (a->rate * a->tau_de));
a->onem_mde = 1.0 - a->mde;
a->deemph_z = 0.0;
}
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT
void SetRXAWFMLimRun (int channel, int run)
{
WFMD a = rxa[channel].wfmd.p;
EnterCriticalSection (&ch[channel].csDSP);
if (a->lim_run != run)
{
a->lim_run = run;
}
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT
void SetRXAWFMLimGain (int channel, double gaindB)
{
double gain = pow(10.0, gaindB / 20.0);
WFMD a = rxa[channel].wfmd.p;
EnterCriticalSection (&ch[channel].csDSP);
if (a->lim_gain != gain)
{
decalc_wfmd (a);
a->lim_gain = gain;
calc_wfmd (a);
}
LeaveCriticalSection (&ch[channel].csDSP);
}
+103
View File
@@ -0,0 +1,103 @@
/* wfmd.h
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2013 Warren Pratt, NR0V
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#ifndef _wfmd_h
#define _wfmd_h
#include "firmin.h"
#include "wcpAGC.h"
typedef struct _wfmd
{
int run;
int size;
double* in;
double* out;
double rate;
double f_low; // audio low cutoff
double f_high; // audio high cutoff
// quadrature discriminator
double deviation; // peak deviation, Hz
double again; // discriminator output gain
double pre_i; // previous sample, I
double pre_q; // previous sample, Q
// for dc removal
double tau;
double mtau;
double onem_mtau;
double fmdc;
// de-emphasis, single-pole RC
int deemph_run;
double tau_de; // 75.0e-6 (Americas) or 50.0e-6 (elsewhere)
double mde;
double onem_mde;
double deemph_z;
// for audio filter
double* audio;
FIRCORE paud;
int nc_aud;
int mp_aud;
double afgain;
// detector limiter
WCPAGC plim;
int lim_run;
double lim_gain;
double lim_pre_gain;
} wfmd, *WFMD;
extern WFMD create_wfmd ( int run, int size, double* in, double* out, int rate, double deviation,
double f_low, double f_high, double tau, int deemph_run, double tau_de, double afgain,
int nc_aud, int mp_aud);
extern void destroy_wfmd (WFMD a);
extern void flush_wfmd (WFMD a);
extern void xwfmd (WFMD a);
extern void setBuffers_wfmd (WFMD a, double* in, double* out);
extern void setSamplerate_wfmd (WFMD a, int rate);
extern void setSize_wfmd (WFMD a, int size);
// RXA Properties
extern __declspec (dllexport) void SetRXAWFMDeviation (int channel, double deviation);
extern __declspec (dllexport) void SetRXAWFMNCaud (int channel, int nc);
extern __declspec (dllexport) void SetRXAWFMMPaud (int channel, int mp);
extern __declspec (dllexport) void SetRXAWFMAFFilter (int channel, double low, double high);
extern __declspec (dllexport) void SetRXAWFMDeemphRun (int channel, int run);
extern __declspec (dllexport) void SetRXAWFMDeemphTau (int channel, double tau);
extern __declspec (dllexport) void SetRXAWFMLimRun (int channel, int run);
extern __declspec (dllexport) void SetRXAWFMLimGain (int channel, double gaindB);
#endif
+250
View File
@@ -0,0 +1,250 @@
/* wfmmod.c
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2013, 2016, 2023 Warren Pratt, NR0V
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#include "comm.h"
// the modulated spectrum spans +/-(deviation + f_high); at the sample rates used for
// narrowband modes that exceeds Nyquist, so the bandpass degenerates to a passthrough.
static double bpfc_wfmmod (double samplerate, double deviation, double f_high)
{
double fc = deviation + f_high;
double max_fc = 0.45 * samplerate;
if (fc > max_fc) fc = max_fc;
return fc;
}
void calc_wfmmod (WFMMOD a)
{
// pre-emphasis
if (a->tau_pre > 0.0) a->pmult = exp(-1.0 / (a->samplerate * a->tau_pre));
else a->pmult = 0.0;
a->pnorm = 1.0 / (1.0 - a->pmult);
a->pre_z = 0.0;
// mod
a->sphase = 0.0;
a->sdelta = TWOPI * a->deviation / a->samplerate;
// bandpass
a->bp_fc = bpfc_wfmmod (a->samplerate, a->deviation, a->f_high);
}
WFMMOD create_wfmmod (int run, int size, double* in, double* out, int rate, double dev, double f_low, double f_high,
int pre_run, double tau_pre, int bp_run, int nc, int mp)
{
WFMMOD a = (WFMMOD) malloc0 (sizeof (wfmmod));
double* impulse;
a->run = run;
a->size = size;
a->in = in;
a->out = out;
a->samplerate = (double)rate;
a->deviation = dev;
a->f_low = f_low;
a->f_high = f_high;
a->pre_run = pre_run;
a->tau_pre = tau_pre;
a->bp_run = bp_run;
a->nc = nc;
a->mp = mp;
calc_wfmmod (a);
impulse = fir_bandpass(a->nc, -a->bp_fc, +a->bp_fc, a->samplerate, 0, 1, 1.0 / (2 * a->size));
a->p = create_fircore (a->size, a->out, a->out, a->nc, a->mp, impulse);
_aligned_free (impulse);
return a;
}
void destroy_wfmmod (WFMMOD a)
{
destroy_fircore (a->p);
_aligned_free (a);
}
void flush_wfmmod (WFMMOD a)
{
a->pre_z = 0.0;
a->sphase = 0.0;
flush_fircore (a->p);
}
void xwfmmod (WFMMOD a)
{
int i;
double aud, dp;
if (a->run)
{
for (i = 0; i < a->size; i++)
{
aud = a->in[2 * i + 0];
if (a->pre_run)
{
dp = a->pnorm * (aud - a->pmult * a->pre_z);
a->pre_z = aud;
aud = dp;
}
dp = aud * a->sdelta;
a->sphase += dp;
// at the wide deviation, |dp| exceeds TWOPI once samplerate < deviation,
// so one subtraction is not enough to bring sphase back into range
while (a->sphase >= TWOPI) a->sphase -= TWOPI;
while (a->sphase < 0.0 ) a->sphase += TWOPI;
a->out[2 * i + 0] = 0.7071 * cos (a->sphase);
a->out[2 * i + 1] = 0.7071 * sin (a->sphase);
}
if (a->bp_run)
xfircore (a->p);
}
else if (a->in != a->out)
memcpy (a->out, a->in, a->size * sizeof (complex));
}
void setBuffers_wfmmod (WFMMOD a, double* in, double* out)
{
a->in = in;
a->out = out;
calc_wfmmod (a);
setBuffers_fircore (a->p, a->out, a->out);
}
void setSamplerate_wfmmod (WFMMOD a, int rate)
{
double* impulse;
a->samplerate = rate;
calc_wfmmod (a);
impulse = fir_bandpass(a->nc, -a->bp_fc, +a->bp_fc, a->samplerate, 0, 1, 1.0 / (2 * a->size));
setImpulse_fircore (a->p, impulse, 1);
_aligned_free (impulse);
}
void setSize_wfmmod (WFMMOD a, int size)
{
double* impulse;
a->size = size;
calc_wfmmod (a);
setSize_fircore (a->p, a->size);
impulse = fir_bandpass(a->nc, -a->bp_fc, +a->bp_fc, a->samplerate, 0, 1, 1.0 / (2 * a->size));
setImpulse_fircore (a->p, impulse, 1);
_aligned_free (impulse);
}
/********************************************************************************************************
* *
* TXA Properties *
* *
********************************************************************************************************/
PORT
void SetTXAWFMDeviation (int channel, double deviation)
{
WFMMOD a = txa[channel].wfmmod.p;
double bp_fc = bpfc_wfmmod (a->samplerate, deviation, a->f_high);
double* impulse = fir_bandpass (a->nc, -bp_fc, +bp_fc, a->samplerate, 0, 1, 1.0 / (2 * a->size));
setImpulse_fircore (a->p, impulse, 0);
_aligned_free (impulse);
EnterCriticalSection (&ch[channel].csDSP);
a->deviation = deviation;
// mod
a->sphase = 0.0;
a->sdelta = TWOPI * a->deviation / a->samplerate;
// bandpass
a->bp_fc = bp_fc;
setUpdate_fircore (a->p);
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT
void SetTXAWFMNC (int channel, int nc)
{
WFMMOD a;
double* impulse;
EnterCriticalSection (&ch[channel].csDSP);
a = txa[channel].wfmmod.p;
if (a->nc != nc)
{
a->nc = nc;
impulse = fir_bandpass (a->nc, -a->bp_fc, +a->bp_fc, a->samplerate, 0, 1, 1.0 / (2 * a->size));
setNc_fircore (a->p, a->nc, impulse);
_aligned_free (impulse);
}
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT
void SetTXAWFMMP (int channel, int mp)
{
WFMMOD a;
a = txa[channel].wfmmod.p;
if (a->mp != mp)
{
a->mp = mp;
setMp_fircore (a->p, a->mp);
}
}
PORT
void SetTXAWFMAFFreqs (int channel, double low, double high)
{
WFMMOD a;
double* impulse;
EnterCriticalSection (&ch[channel].csDSP);
a = txa[channel].wfmmod.p;
if (a->f_low != low || a->f_high != high)
{
a->f_low = low;
a->f_high = high;
a->bp_fc = bpfc_wfmmod (a->samplerate, a->deviation, a->f_high);
impulse = fir_bandpass (a->nc, -a->bp_fc, +a->bp_fc, a->samplerate, 0, 1, 1.0 / (2 * a->size));
setImpulse_fircore (a->p, impulse, 1);
_aligned_free (impulse);
}
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT
void SetTXAWFMPreEmphRun (int channel, int run)
{
WFMMOD a = txa[channel].wfmmod.p;
EnterCriticalSection (&ch[channel].csDSP);
if (a->pre_run != run)
{
a->pre_run = run;
a->pre_z = 0.0;
}
LeaveCriticalSection (&ch[channel].csDSP);
}
PORT
void SetTXAWFMPreEmphTau (int channel, double tau)
{
WFMMOD a = txa[channel].wfmmod.p;
EnterCriticalSection (&ch[channel].csDSP);
if (a->tau_pre != tau && tau > 0.0)
{
a->tau_pre = tau;
a->pmult = exp(-1.0 / (a->samplerate * a->tau_pre));
a->pnorm = 1.0 / (1.0 - a->pmult);
a->pre_z = 0.0;
}
LeaveCriticalSection (&ch[channel].csDSP);
}
+86
View File
@@ -0,0 +1,86 @@
/* wfmmod.h
This file is part of a program that implements a Software-Defined Radio.
Copyright (C) 2013, 2016, 2023 Warren Pratt, NR0V
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
The author can be reached by email at
warren@wpratt.com
*/
#ifndef _wfmmod_h
#define _wfmmod_h
#include "firmin.h"
typedef struct _wfmmod
{
int run;
int size;
double* in;
double* out;
double samplerate;
double deviation;
double f_low;
double f_high;
// pre-emphasis, single-pole RC; inverse of the receiver's de-emphasis
int pre_run;
double tau_pre; // 75.0e-6 (Americas) or 50.0e-6 (elsewhere)
double pmult;
double pnorm;
double pre_z;
// mod
double sphase;
double sdelta;
// bandpass
int bp_run;
double bp_fc;
int nc;
int mp;
FIRCORE p;
}wfmmod, *WFMMOD;
extern WFMMOD create_wfmmod (int run, int size, double* in, double* out, int rate, double dev, double f_low, double f_high,
int pre_run, double tau_pre, int bp_run, int nc, int mp);
extern void destroy_wfmmod (WFMMOD a);
extern void flush_wfmmod (WFMMOD a);
extern void xwfmmod (WFMMOD a);
extern void setBuffers_wfmmod (WFMMOD a, double* in, double* out);
extern void setSamplerate_wfmmod (WFMMOD a, int rate);
extern void setSize_wfmmod (WFMMOD a, int size);
// TXA Properties
extern __declspec (dllexport) void SetTXAWFMDeviation (int channel, double deviation);
extern __declspec (dllexport) void SetTXAWFMNC (int channel, int nc);
extern __declspec (dllexport) void SetTXAWFMMP (int channel, int mp);
extern __declspec (dllexport) void SetTXAWFMAFFreqs (int channel, double low, double high);
extern __declspec (dllexport) void SetTXAWFMPreEmphRun (int channel, int run);
extern __declspec (dllexport) void SetTXAWFMPreEmphTau (int channel, double tau);
#endif