# `NxSignal`
[🔗](https://github.com/elixir-nx/nx_signal/blob/v0.4.0/nx-signal/lib/nx_signal.ex#L1)

Nx library extension for digital signal processing.

# `fft_frequencies`

Computes the frequency bins for a FFT with given options.

## Arguments

  * `sampling_rate` - Sampling frequency in Hz.

## Options

  * `:fft_length` - Number of FFT frequency bins.
  * `:type` - Optional output type. Defaults to `{:f, 32}`
  * `:name` - Optional axis name for the tensor. Defaults to `:frequencies`

## Examples

    iex> NxSignal.fft_frequencies(1.6e4, fft_length: 10)
    #Nx.Tensor<
      f32[frequencies: 10]
      [0.0, 1.6e3, 3.2e3, 4.8e3, 6.4e3, 8e3, 9.6e3, 1.12e4, 1.28e4, 1.44e4]
    >

# `istft`

Computes the Inverse Short-Time Fourier Transform of a tensor.

Returns a tensor of M time-domain frames of length `fft_length`.

See also: `NxSignal.Windows`, `stft/3`

## Options

  * `:fft_length` - the DFT length that will be passed to `Nx.fft/2`. Defaults to `:power_of_two`.
  * `:overlap_length` - the number of samples for the overlap between frames.
    Defaults to half the window size.
  * `:sampling_rate` - the sampling rate $F_s$ in Hz. Defaults to `1000`.
  * `:scaling` - `nil`, `:spectrum` or `:psd`.
    * `:spectrum` - each frame is multiplied by $\sum_{i} window[i]$.
    * `nil` - No scaling is applied.
    * `:psd` - each frame is multiplied by $\sqrt{F\_s\sum_{i} window[i]^2}$.

## Examples

In general, `istft/3` takes in the same parameters and window as the `stft/3` that generated the spectrum.
In the first example, we can notice that the reconstruction is mostly perfect, aside from the first sample.

This is because the Hann window only ensures perfect reconstruction in overlapping regions, so the edges
of the signal end up being distorted.

    iex> t = Nx.tensor([10, 10, 1, 0, 10, 10, 2, 20])
    iex> w = NxSignal.Windows.hann(4)
    iex> opts = [sampling_rate: 1, fft_length: 4]
    iex> {z, _time, _freqs} = NxSignal.stft(t, w, opts)
    iex> result = NxSignal.istft(z, w, opts)
    iex> Nx.as_type(result, Nx.type(t))
    #Nx.Tensor<
      s32[8]
      [0, 10, 1, 0, 10, 10, 2, 20]
    >

Different scaling options are available (see `stft/3` for a more detailed explanation).
For perfect reconstruction, you want to use the same scaling as the STFT:

    iex> t = Nx.tensor([10, 10, 1, 0, 10, 10, 2, 20])
    iex> w = NxSignal.Windows.hann(4)
    iex> opts = [scaling: :spectrum, sampling_rate: 1, fft_length: 4]
    iex> {z, _time, _freqs} = NxSignal.stft(t, w, opts)
    iex> result = NxSignal.istft(z, w, opts)
    iex> Nx.as_type(result, Nx.type(t))
    #Nx.Tensor<
      s32[8]
      [0, 10, 1, 0, 10, 10, 2, 20]
    >

    iex> t = Nx.tensor([10, 10, 1, 0, 10, 10, 2, 20], type: :f32)
    iex> w = NxSignal.Windows.hann(4)
    iex> opts = [scaling: :psd, sampling_rate: 1, fft_length: 4]
    iex> {z, _time, _freqs} = NxSignal.stft(t, w, opts)
    iex> result = NxSignal.istft(z, w, opts)
    iex> Nx.as_type(result, Nx.type(t))
    #Nx.Tensor<
      f32[8]
      [0.0, 10.0, 0.99999994, -2.1900146e-7, 10.0, 10.0, 2.0000002, 20.0]
    >

# `mel_filters`

Generates weights for converting an STFT representation into MEL-scale.

See also: `stft/3`, `istft/3`, `stft_to_mel/3`

## Arguments

  * `fft_length` - Number of FFT bins
  * `mel_bins` - Number of target MEL bins
  * `sampling_rate` - Sampling frequency in Hz

## Options
  * `:max_mel` - the pitch for the last MEL bin before log scaling. Defaults to 3016
  * `:mel_frequency_spacing` - the distance in Hz between two MEL bins before log scaling. Defaults to 66.6
  * `:type` - Target output type. Defaults to `{:f, 32}`

## Examples

    iex> NxSignal.mel_filters(10, 5, 8.0e3)
    #Nx.Tensor<
      f32[mels: 5][frequencies: 10]
      [
        [0.0, 8.129208e-4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
        [0.0, 9.972017e-4, 2.1870289e-4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
        [0.0, 0.0, 9.510892e-4, 4.1505092e-4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
        [0.0, 0.0, 0.0, 4.035892e-4, 5.276656e-4, 2.574124e-4, 0.0, 0.0, 0.0, 0.0],
        [0.0, 0.0, 0.0, 0.0, 7.329034e-5, 2.3422057e-4, 3.8295105e-4, 2.871204e-4, 1.9128979e-4, 9.545916e-5]
      ]
    >

# `stft`

Computes the Short-Time Fourier Transform of a tensor.

Returns the complex spectrum Z, the time in seconds for
each frame and the frequency bins in Hz.

The STFT is parameterized through:

  * $k$: length of the Discrete Fourier Transform (DFT)
  * $N$: length of each frame
  * $H$: hop (in samples) between frames (calculated as $H = N - \text{overlap\\_length}$)
  * $M$: number of frames
  * $x[n]$: the input time-domain signal
  * $w[n]$: the window function to be applied to each frame

$$
DFT(x, w) := \sum_{n=0}^{N - 1} x[n]w[n]e^\frac{-2 \pi i k n}{N} \\\\
X[m, k] = DFT(x[mH..(mH + N - 1)], w)
$$

where $m$ assumes all values in the interval $[0, M - 1]$

See also: `NxSignal.Windows`, `istft/3`, `stft_to_mel/3`

## Options

  * `:sampling_rate` - the sampling frequency $F_s$ for the input in Hz. Defaults to `1000`.
  * `:fft_length` - the DFT length that will be passed to `Nx.fft/2`. Defaults to `:power_of_two`.
  * `:overlap_length` - the number of samples for the overlap between frames.
    Defaults to half the window size.
  * `:window_padding` - `:reflect`, `:zeros` or `nil`. See `as_windowed/3` for more details.
  * `:scaling` - `nil`, `:spectrum` or `:psd`.
    * `:spectrum` - each frame is divided by $\sum_{i} window[i]$.
    * `nil` - No scaling is applied.
    * `:psd` - each frame is divided by $\sqrt{F\_s\sum_{i} window[i]^2}$.

## Examples

    iex> {z, t, f} = NxSignal.stft(Nx.iota({4}), NxSignal.Windows.rectangular(2), overlap_length: 1, fft_length: 2, sampling_rate: 400)
    iex> z
    #Nx.Tensor<
      c64[frames: 3][frequencies: 2]
      [
        [1.0+0.0i, -1.0+0.0i],
        [3.0+0.0i, -1.0+0.0i],
        [5.0+0.0i, -1.0+0.0i]
      ]
    >
    iex> t
    #Nx.Tensor<
      f32[frames: 3]
      [0.0025, 0.005, 0.0075]
    >
    iex> f
    #Nx.Tensor<
      f32[frequencies: 2]
      [0.0, 200.0]
    >

# `stft_to_mel`

Converts a given STFT time-frequency spectrum into a MEL-scale time-frequency spectrum.

See also: `stft/3`, `istft/3`, `mel_filters/4`

## Arguments

  * `z` - STFT spectrum
  * `sampling_rate` - Sampling frequency in Hz

## Options

  * `:fft_length` - Number of FFT bins
  * `:mel_bins` - Number of target MEL bins. Defaults to 128
  * `:type` - Target output type. Defaults to `{:f, 32}`

## Examples

    iex> fft_length = 16
    iex> sampling_rate = 8.0e3
    iex> {z, _, _} = NxSignal.stft(Nx.iota({10}), NxSignal.Windows.hann(4), overlap_length: 2, fft_length: fft_length, sampling_rate: sampling_rate, window_padding: :reflect)
    iex> Nx.axis_size(z, :frequencies)
    16
    iex> Nx.axis_size(z, :frames)
    6
    iex> NxSignal.stft_to_mel(z, sampling_rate, fft_length: fft_length, mel_bins: 4)
    #Nx.Tensor<
      f32[frames: 6][mel: 4]
      [
        [0.29005307, 0.17422175, 0.18422472, 0.09807998],
        [0.6093881, 0.5647397, 0.43538243, 0.086352706],
        [0.75841033, 0.70850146, 0.5636921, 0.17911881],
        [0.8461772, 0.7952491, 0.64707625, 0.25204098],
        [0.9085489, 0.85726047, 0.70786566, 0.30867678],
        [0.9085489, 0.85726047, 0.70786566, 0.30867678]
      ]
    >

# `as_windowed`

Returns a tensor of K windows of length N

## Options

  * `:window_length` - the number of samples in a window
  * `:stride` - The number of samples to skip between windows. Defaults to `1`.
  * `:padding` - Padding mode, can be `:reflect` or a valid padding as per `Nx.pad/3` over the
    input tensor's shape. Defaults to `:valid`. If `:reflect` or `:same`, the first window will be centered
    at the start of the signal. The padding is applied for the whole input, rather than individual
    windows. For `:zeros`, effectively each incomplete window will be zero-padded.

## Examples

    iex> NxSignal.as_windowed(Nx.tensor([0, 1, 2, 3, 4, 10, 11, 12]), window_length: 4)
    #Nx.Tensor<
      s32[5][4]
      [
        [0, 1, 2, 3],
        [1, 2, 3, 4],
        [2, 3, 4, 10],
        [3, 4, 10, 11],
        [4, 10, 11, 12]
      ]
    >

    iex> NxSignal.as_windowed(Nx.tensor([0, 1, 2, 3, 4, 10, 11, 12]), window_length: 3)
    #Nx.Tensor<
      s32[6][3]
      [
        [0, 1, 2],
        [1, 2, 3],
        [2, 3, 4],
        [3, 4, 10],
        [4, 10, 11],
        [10, 11, 12]
      ]
    >

    iex> NxSignal.as_windowed(Nx.tensor([0, 1, 2, 3, 4, 10, 11]), window_length: 2, stride: 2, padding: [{0, 3}])
    #Nx.Tensor<
      s32[5][2]
      [
        [0, 1],
        [2, 3],
        [4, 10],
        [11, 0],
        [0, 0]
      ]
    >

    iex> t = Nx.iota({7});
    iex> NxSignal.as_windowed(t, window_length: 6, padding: :reflect, stride: 1)
    #Nx.Tensor<
      s32[8][6]
      [
        [3, 2, 1, 0, 1, 2],
        [2, 1, 0, 1, 2, 3],
        [1, 0, 1, 2, 3, 4],
        [0, 1, 2, 3, 4, 5],
        [1, 2, 3, 4, 5, 6],
        [2, 3, 4, 5, 6, 5],
        [3, 4, 5, 6, 5, 4],
        [4, 5, 6, 5, 4, 3]
      ]
    >

    iex> NxSignal.as_windowed(Nx.iota({10}), window_length: 6, padding: :reflect, stride: 2)
    #Nx.Tensor<
      s32[6][6]
      [
        [3, 2, 1, 0, 1, 2],
        [1, 0, 1, 2, 3, 4],
        [1, 2, 3, 4, 5, 6],
        [3, 4, 5, 6, 7, 8],
        [5, 6, 7, 8, 9, 8],
        [7, 8, 9, 8, 7, 6]
      ]
    >

# `overlap_and_add`

Performs the overlap-and-add algorithm over
an {..., M, N}-shaped tensor, where M is the number of
windows and N is the window size.

The tensor is zero-padded on the right so
the last window fully appears in the result.

## Options

  * `:overlap_length` - The number of overlapping samples between windows
  * `:type` - output type for casting the accumulated result.
    If not given, defaults to `Nx.Type.to_complex/1` called on the input type.

## Examples

    iex> NxSignal.overlap_and_add(Nx.iota({3, 4}), overlap_length: 0)
    #Nx.Tensor<
      s32[12]
      [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
    >

    iex> NxSignal.overlap_and_add(Nx.iota({3, 4}), overlap_length: 3)
    #Nx.Tensor<
      s32[6]
      [0, 5, 15, 18, 17, 11]
    >

    iex> t = Nx.tensor([[[[0, 1, 2, 3], [4, 5, 6, 7]]], [[[10, 11, 12, 13], [14, 15, 16, 17]]]]) |> Nx.vectorize(x: 2, y: 1)
    iex> NxSignal.overlap_and_add(t, overlap_length: 3)
    #Nx.Tensor<
      vectorized[x: 2][y: 1]
      s32[5]
      [
        [
          [0, 5, 7, 9, 7]
        ],
        [
          [10, 25, 27, 29, 17]
        ]
      ]
    >

# `czt`

Chirp Z-Transform.

Evaluates the z-transform of `x` on a spiral contour in the z-plane,
returning $M$ output points. Uses Bluestein's identity to express the
sum as a convolution, giving $O((N+M)\log(N+M))$ complexity.

$$
X[k] = \sum_{n=0}^{N-1} x[n] \, A^{-n} \, W^{nk}, \quad k = 0, 1, \ldots, M-1
$$

When $A = 1$, $W = e^{-j2\pi/M}$, and $M = N$, the result is identical
to the standard DFT.

See also: `zoom_fft/4`

## Options

  * `:output_length` - number of output points $M$. Defaults to `Nx.size(x)`.
  * `:contour_ratio` - contour ratio $W$ (complex scalar tensor).
    Defaults to $e^{-j2\pi/M}$, which gives the DFT.
  * `:contour_start` - contour starting point $A$ (complex scalar tensor).
    Defaults to $1$.

## Examples

    iex> NxSignal.czt(Nx.tensor([1.0, 0.0, 0.0, 0.0]))
    #Nx.Tensor<
      c64[4]
      [1.0-1.5893256e-8i, 0.99999994+0.0i, 1.0+1.5893256e-8i, 0.99999994+0.0i]
    >

# `zoom_fft`

Zoom FFT: high-resolution DFT over a narrow frequency band.

Computes $M$ output samples of the DFT concentrated in the normalised
frequency range $[f_1, f_2]$. This is a thin wrapper around `czt/2` that
sets:

$$
A = e^{j2\pi f_1}, \quad W = e^{-j2\pi(f_2 - f_1)/M}
$$

The output frequencies are $f_k = f_1 + k\,(f_2 - f_1)/M$ for $k = 0, \ldots, M-1$.

See also: `czt/2`

## Arguments

  * `x`  - input signal, shape `{n}`.
  * `f1` - lower bound of the frequency range (normalised, 0 to 1).
  * `f2` - upper bound of the frequency range (normalised, 0 to 1).

## Options

  * `:output_length` - number of output points $M$. Defaults to `Nx.size(x)`.

## Examples

    iex> NxSignal.zoom_fft(Nx.tensor([1.0, 0.0, 0.0, 0.0]), 0.0, 1.0) |> Nx.real() |> Nx.round()
    #Nx.Tensor<
      f32[4]
      [1.0, 1.0, 1.0, 1.0]
    >

---

*Consult [api-reference.md](api-reference.md) for complete listing*
