Source code for adctoolbox.spectrum.quick_sndr

"""
Lean SNDR + ENOB computation.

Uses a direct FFT ratio path for optimization loops and spec gates. This keeps
the metric definition aligned with compute_spectrum for explicit coherent
side-bin settings without running harmonic or spur analysis. Pass
``side_bin="auto"`` to opt into the heavier auto side-bin detector used by
``compute_spectrum(..., side_bin=None)``.
"""

import numpy as np

from adctoolbox.spectrum._bin_ranges import rfft_inband_bin_count
from adctoolbox.spectrum._locate_fundamental import _locate_fundamental
from adctoolbox.spectrum._prepare_fft_input import _prepare_fft_input
from adctoolbox.spectrum._side_bin_auto import _detect_side_bin_auto
from adctoolbox.spectrum._spectrum_averaging import _power_average
from adctoolbox.spectrum._window import (
    _calculate_power_correction,
    _create_window,
    _get_auto_side_bin_fallback,
    _get_default_side_bin,
)


[docs] def quick_sndr(data, fs=1.0, win_type="hann", side_bin=None, max_scale_range=None): """ SNDR + ENOB from a single 1-D capture (same SNDR definition as analyze_spectrum). Parameters ---------- data Time-domain samples, shape (N,) fs Sample rate (Hz) win_type Window name ('hann', 'rectangular', ...) side_bin Side bins around fundamental. None uses the window's coherent default fast path. "auto" uses waveform-based side-bin detection matching ``compute_spectrum(..., side_bin=None)``; this adds an extra ideal-tone FFT pass and assumes a dominant single-tone capture. For multi-tone, strongly distorted, or very noisy captures, pass an explicit integer. max_scale_range Optional full-scale range for input normalization Returns ------- dict ``{'sndr_dbc': float, 'enob': float}`` """ data_normalized = _prepare_fft_input(data, max_scale_range) _, n_fft = data_normalized.shape n_inband = rfft_inband_bin_count(n_fft, osr=1) window_vector, window_gain, equiv_noise_bw_factor = _create_window(win_type, n_fft) power_spectrum, _ = _power_average(data_normalized * window_vector) power_correction = _calculate_power_correction(window_gain, equiv_noise_bw_factor) power_spectrum *= power_correction fundamental_bin, fundamental_bin_fractional = _locate_fundamental(power_spectrum, n_inband) if isinstance(side_bin, str) and side_bin == "auto": side_bin = _detect_side_bin_auto( power_spectrum, fundamental_bin, fundamental_bin_fractional, n_inband, n_fft, window_vector, power_correction, fallback_side_bin=_get_auto_side_bin_fallback(win_type), minimum_side_bin=_get_default_side_bin(win_type), ) elif side_bin is None: side_bin = _get_default_side_bin(win_type) side_bin = int(max(side_bin, 0)) sig_bin_start = max(fundamental_bin - side_bin, 0) sig_bin_end = min(fundamental_bin + side_bin + 1, n_inband) sig_linear = float(np.sum(power_spectrum[sig_bin_start:sig_bin_end])) noise_distortion_spectrum = power_spectrum.copy() noise_distortion_spectrum[: min(side_bin, len(noise_distortion_spectrum))] = 0.0 noise_distortion_spectrum[sig_bin_start:sig_bin_end] = 0.0 noise_distortion = float(np.sum(noise_distortion_spectrum[:n_inband])) sndr_dbc = 10 * np.log10(sig_linear / (noise_distortion + 1e-20)) enob = (sndr_dbc - 1.76) / 6.02 return {"sndr_dbc": float(sndr_dbc), "enob": float(enob)}