<!-- canonical: https://jmrplens.github.io/phonometry/reference/api/signals/synchronous-average/ -->
Source: https://jmrplens.github.io/phonometry/reference/api/signals/synchronous-average/

Time synchronous averaging (TSA) of a periodic waveform in noise.

Time domain averaging extracts a repetitive signal of known period `T`
from additive noise by ensemble-averaging successive length-`T` blocks,
following P. D. McFadden, "A revised model for the extraction of periodic
waveforms by time domain averaging", *Mechanical Systems and Signal
Processing* 1(1) 1987, 83-95. Given a signal
$y(t) = x(t) + e(t)$ with `x` periodic in `T` and `e`
asynchronous, the average

$$
a(t) = \frac{1}{N} \sum_{n=0}^{N-1} y(t + n T) \tag{McFadden Eq. 5}
$$

reinforces every component synchronous with `T` and suppresses the rest.

**Two models, one implementation.** McFadden distinguishes the *existing*
comb-filter model from the *revised* model. In the frequency domain the
average is the multiplication of $Y(f)$ by the comb filter (Eq. 8)

$$
C(f) = \frac{1}{N} \, \frac{\sin(N \pi f T)}{\sin(\pi f T)}
$$

whose magnitude
$\lvert C(f) \rvert = \lvert \sin(N \pi f T) / (N \sin(\pi f T)) \rvert$ is a Dirichlet
kernel: unity at every harmonic $k/T$ (the teeth, Eq. 9, of unit
height regardless of `N`) and zero at the nodes $j/(N T)$ with
`j` not a
multiple of `N`. That model assumes knowledge of `y` over infinite time
and produces a result that is not exactly periodic. McFadden's *revised*
model applies a rectangular window of width `T` in the time domain and
samples the transform in the frequency domain, so it needs only a finite
block of the signal and yields a result that is exactly periodic and can be
stored as a single period. The digital block average computed here, `N`
consecutive periods of an integer number of samples reduced to one period,
*is* that revised model: the returned `period_waveform`, repeated,
is exactly periodic.

**Noise reduction.** Asynchronous noise of variance $\sigma^2$
averaged over `N` periods has residual variance $\sigma^2/N$:
the residual standard deviation falls as $1/\sqrt{N}$ and the
amplitude signal-to-noise ratio improves by $\sqrt{N}$ (a power
reduction of $10 \log_{10} N$ dB, reported as
`noise_reduction_db`).

**Choosing N (McFadden's revised-model correction).** Because a discrete
interfering tone at a *non-harmonic* order $q = f T$ is only
attenuated, not removed, its rejection is optimised by choosing `N` so
that a comb node lands exactly on it, i.e. the smallest `N` with
$N q$ an integer.
McFadden's own example, a tone at 32.05 orders, is suppressed by more
than 100 dB with $N = 20$ (since $20 \cdot 32.05 = 641$)
yet -- evaluating Eq. 8
at that order -- by only about 14 dB with the common power-of-two choice
$N = 32$ ($32 \cdot 32.05 = 1025.6$; the paper makes the
comparison but does
not print this figure). Thus the habit of taking a power-of-two number of
averages is not, in general, optimal.

**Non-integer samples per period.** When $f_\mathrm{s} T$ is not an integer
the period boundaries fall between samples. Each block is then aligned to
a common integer grid by the band-limited fractional delay of
[`phonometry.signals.test_signals.fractional_delay`](/phonometry/reference/api/signals/test-signals/#fractional_delay) before averaging, so
the periodic waveform is recovered within the interpolation error of that
band-limited shift. An integer $f_\mathrm{s} T$ needs no interpolation and
the waveform is recovered to machine precision. The averaged samples stay
on the $1/f_\mathrm{s}$ sampling grid throughout: output sample `m` is the
average of the input at the times $n T + m/f_\mathrm{s}$, so the returned
time axis is $m/f_\mathrm{s}$ and the
$M = \operatorname{round}(f_\mathrm{s} T)$ samples cover one period exactly
when $f_\mathrm{s} T$ is an integer and to within half a sample otherwise.
No resampling onto an `M`-point angular grid is performed.

> Auto-generated from the source docstrings by `scripts/generate_api_docs.py` (`make api-docs`). Do not edit by hand.

## comb_filter_response

```python
comb_filter_response(
    frequencies: NDArray[np.float64] | list[float],
    period: float,
    n_averages: int,
) -> NDArray[np.float64]
```

Magnitude of the N-period synchronous-averaging comb filter.

The closed form of McFadden Eq. 8,
$\lvert C(f) \rvert = \lvert \sin(N \pi f T) / (N \sin(\pi f T)) \rvert$,
a Dirichlet kernel with unit-height teeth at the
harmonics $k/T$ (Eq. 9) and nodes at $j/(N T)$ for `j`
not a multiple of `N`.

**Parameters**

| Name | Description |
| :--- | :--- |
| `frequencies` | Frequencies at which to evaluate, in Hz. |
| `period` | Repetition period `T`, in seconds. |
| `n_averages` | Number of averaged periods `N` (at least 1). |

**Returns:** The filter magnitude at each frequency (unitless; bounded by 1, though floating-point cancellation immediately beside a tooth can return values above 1 by a few parts in 1e9).

**Raises**

| Exception | When |
| :--- | :--- |
| ValueError | If the parameters are invalid. |

## SynchronousAverageResult

```python
SynchronousAverageResult(
    period_waveform: NDArray[np.float64],
    times: NDArray[np.float64],
    residual: NDArray[np.float64],
    n_averages: int,
    samples_per_period: int,
    period: float,
    fs: float,
    interpolated: bool,
    noise_reduction_db: float,
    residual_rms: float,
    comb_frequencies: NDArray[np.float64],
    comb_response: NDArray[np.float64],
)
```

Time synchronous average of a periodic waveform in noise.

**Attributes**

| Name | Description |
| :--- | :--- |
| `period_waveform` | The averaged periodic waveform, one period of `samples_per_period` samples. |
| `times` | Time axis of `period_waveform`, in seconds: the sampling grid $m/f_\mathrm{s}$, $m = 0 \ldots M-1$ (the averaged samples stay on the $1/f_\mathrm{s}$ grid; see the module note). The axis spans one period exactly when $f_\mathrm{s} T$ is an integer, and to within half a sample otherwise. |
| `residual` | Input minus the periodic reconstruction, over the analysed span (`n_averages * samples_per_period` samples, aligned to the integer period grid): what is left after the synchronous component is removed. |
| `n_averages` | Number of periods averaged, `N`. |
| `samples_per_period` | Integer samples per period `M` after any alignment. |
| `period` | Repetition period `T`, in seconds. |
| `fs` | Sample rate, in Hz. |
| `interpolated` | Whether band-limited fractional-delay alignment was applied (`True` when $f_\mathrm{s} T$ is not an integer). |
| `noise_reduction_db` | Power reduction of asynchronous noise, $10 \log_{10} N$ dB (amplitude SNR gain $\sqrt{N}$). |
| `residual_rms` | Root-mean-square of `residual`. |
| `comb_frequencies` | Frequency axis of the comb-filter response, in Hz (from DC over a whole number of harmonics of `1/T`). |
| `comb_response` | Magnitude of the comb filter (McFadden Eq. 8) on `comb_frequencies`. |

### SynchronousAverageResult.amplitude_snr_gain

*property*

Amplitude signal-to-noise improvement $\sqrt{N}$.

### SynchronousAverageResult.plot()

```python
SynchronousAverageResult.plot(
    ax: Axes | None = None,
    *,
    language: str = 'en',
    **kwargs: Any,
) -> Axes | NDArray[Any]
```

Plot the averaged waveform and the comb-filter magnitude.

With `ax` given, only the averaged-waveform panel is drawn on it.

**Parameters**

| Name | Description |
| :--- | :--- |
| `language` | Label language, `"en"` (default) or `"es"`. |

## time_synchronous_average

```python
time_synchronous_average(
    x: NDArray[np.float64] | list[float],
    fs: float,
    period: float,
    *,
    n_averages: int | None = None,
    n_harmonics: int = 8,
) -> SynchronousAverageResult
```

Extract a periodic waveform of known period by time domain averaging.

Ensemble-averages `N` successive periods of the record (McFadden
Eq. 5) to reinforce the component synchronous with `period` and
suppress asynchronous noise, whose residual standard deviation falls as
$1/\sqrt{N}$. When `fs * period` is an integer the periods are
sliced
directly and a noiseless periodic signal is recovered exactly; otherwise
each period is aligned to a common integer grid by the band-limited
fractional delay of [`fractional_delay`](/phonometry/reference/api/signals/test-signals/#fractional_delay)
and recovered within that interpolation error.

**Parameters**

| Name | Description |
| :--- | :--- |
| `x` | Signal, 1-D, containing the periodic component plus noise. |
| `fs` | Sample rate, in Hz. |
| `period` | Known repetition period `T`, in seconds (e.g. one revolution of a rotating machine). |
| `n_averages` | Number of whole periods to average (default: as many as the record holds). Choosing `N` so that $N q$ is an integer places a comb node on an interfering tone at order `q` and maximises its rejection (McFadden's revised-model result). |
| `n_harmonics` | Number of harmonics of `1/T` spanned by the returned comb-filter response (default 8). |

**Returns:** A [`SynchronousAverageResult`](/phonometry/reference/api/signals/synchronous-average/#synchronousaverageresult).

**Raises**

| Exception | When |
| :--- | :--- |
| ValueError | If the inputs or parameters are invalid. |
