[package]
name        = "mfsk-core"
version     = "0.5.9"
edition     = "2024"
license     = "GPL-3.0-or-later"
description = "Pure-Rust WSJT-family decoders and synthesisers (FT8 FT4 FST4 WSPR JT9 JT65 Q65) behind a zero-cost Protocol trait. Host (rustfft) or no_std embedded targets (ESP32-S3, RP2350, Cortex-M) via a pluggable FFT backend; optional fixed-point hot path for FPU-less MCUs."
repository  = "https://github.com/jl1nie/mfsk-core"
readme      = "../README.md"
keywords    = ["wsjt", "ft8", "q65", "amateur-radio", "embedded"]
categories  = ["science", "encoding", "multimedia::audio", "embedded", "no-std"]

[features]
default      = ["std", "ft8", "ft4", "parallel", "fft-rustfft"]

# Standard-library availability. Pulls in `rustfft` (std-only) and
# enables the rayon-based parallel iterators; required for every
# protocol's wide-band decode path because `rustfft::FftPlanner` is
# the default FFT backend. `no_std + alloc` builds turn this off and
# pick a no_std FFT implementation via the `fft-*` features below.
std          = ["alloc"]

# `no_std + alloc` baseline. All protocol modules that allocate
# (`Vec`, `Box`, `String`) need this; only purely-stack code can
# avoid it. The crate top-level enables `extern crate alloc` whenever
# this feature is on.
alloc        = []

# Aggregate features.
full         = ["std", "fft-rustfft", "ft8", "ft4", "fst4", "wspr", "jt9", "jt65", "q65", "packet-bytes", "uvpacket"]

# FFT backend selection. `mfsk-core::core::fft::FftPlanner` is the
# trait every decode-side caller talks to; one of these features must
# be on whenever decode-side code is compiled (host gets rustfft;
# embedded targets bring their own backend through the extern Rust
# factory `mfsk_core_make_default_fft_planner` — see core::fft docs).
fft-rustfft  = ["std", "dep:rustfft"]
fft-extern   = ["alloc"]

# Embedded integer pipeline: u16 spectrogram + i16 internal DFT +
# Q3i8 LLR + integer NMS BP. Off by default — host stays bit-identical
# to the existing f32 path. Embedded targets turn this on for halved
# PSRAM bandwidth and 6 KB BP scratch (vs the f32 default).
#
# Phase 1 validation (2026-05-03): Q3i8 is recall-equivalent to the
# previously-shipped Q11i16 LLR variant (host + LX6 implementation
# sweep). The Q11i16 scalar type still exists in `core::scalar` for
# manual use / comparison, but no built-in feature wires it into the
# decode hot loop anymore.
fixed-point  = []

# Halve coarse_sync time-axis resolution (NSTEP = NSPS/2 instead of
# NSPS/4 = WSJT-X reference). Embedded targets enable this — at NSPS/4
# the spec memory and stage-1 FFT count both double, which exceeds
# the LX7 post-SlotEnd budget. NSPS/2 was the embedded baseline up
# through 0.5.4 and is what `embedded-shared/stage1_inc` already
# emits. host stays on the WSJT-faithful NSPS/4 default.
nstep-half   = []

# Always-on coarse_sync sub-stage profiling. Same output as setting
# `MFSK_PROFILE_COARSE=1` on host, but works on embedded targets where
# the env var is not reachable. Only useful when investigating stage-2
# wall-clock breakdown — adds 4 timestamp reads per `coarse_sync` call,
# negligible cost.
profile-coarse = ["std"]

# Lightweight modes — embedded-port-clean. `fft-rustfft` (host) or
# `fft-extern` (embedded) is required at the crate level for the
# decode pipeline; the protocol features themselves only enable the
# protocol-specific glue.
ft8          = []
ft4          = []
wspr         = []
packet-bytes = []

# Heavy modes — these still call `rustfft` directly (out of scope of
# the embedded port). Pulling them implies the host build via
# `fft-rustfft` (which transitively requires `std`).
fst4         = ["fft-rustfft"]
jt9          = ["fft-rustfft"]
jt65         = ["fft-rustfft"]
q65          = ["fft-rustfft"]

# uvpacket pulls in fst4 because it reuses the Ldpc240_101 codec
# whose `Wsjt77Message::verify_info` length-dispatch logic (CRC-24
# vs CRC-14) is wired through fst4's path.
uvpacket     = ["fst4"]
parallel     = ["std", "dep:rayon"]

[dependencies]
# `num-complex` carries the `Float` trait via num-traits; under no_std
# we need `libm` to provide sin/cos/sqrt/etc. The std build pulls
# them via num-traits/std (transitively from rustfft) and ignores libm.
num-complex = { version = "0.4", default-features = false, features = ["libm"] }
# Direct dep so call sites can `use num_traits::Float` to get
# `.atanh()` / `.tanh()` / `.sqrt()` / etc. under no_std. The `libm`
# feature provides the actual math impls; under std these become
# no-op forwards to the f32 inherent methods.
num-traits  = { version = "0.2", default-features = false, features = ["libm"] }
rustfft     = { version = "6", optional = true }
crc         = { version = "3", default-features = false }
rayon       = { version = "1", optional = true }

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = [
    'cfg(feature, values("std","alloc","ft8","ft4","fst4","wspr","jt9","jt65","q65","packet-bytes","uvpacket","parallel","full","fft-rustfft","fft-extern","fixed-point","profile-coarse","nstep-half"))',
] }

# Build docs.rs with every protocol feature enabled so users browsing
# the online docs see the complete API. The `--cfg docsrs` flag lets
# us expose feature-gated items with visible "available on crate
# feature …" badges (this build passes it; gate any such annotation
# on `#[cfg(docsrs)]` if we ever add one).
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
