Farrow arbitrary resampler

import numpy as np
import matplotlib.pyplot as plt

import sdr

%config InlineBackend.print_figure_kwargs = {"facecolor" : "w"}
# %matplotlib widget

Construct an input signal, \(x[n] = x(n T_s)\)

Create a discrete-time signal \(x[n]\) that sample the continuous-time signal \(x(t)\) at a sample rate \(f_s = 1 / T_s\).

sample_rate = 1  # samples/s
N = 100  # samples
freq = 0.05  # Hz
tx = np.arange(N) / sample_rate  # Time axis for the input signal
x = np.exp(1j * 2 * np.pi * freq * tx)  # Complex exponential input signal
x *= np.exp(-np.arange(N) / 100)  # Exponential decay
plt.figure(figsize=[10, 5])
plt.plot(tx, x.real, marker="o", fillstyle="none", label="real")
plt.plot(tx, x.imag, marker="o", fillstyle="none", label="imag")
plt.xlabel("Time (s)")
plt.ylabel("Amplitude")
plt.title("Original signal, $x(t)$")
plt.legend()
plt.grid(which="both", linestyle="--")
plt.show()
../../_images/804bebdc546e5f65dc2ab65373ca5d6def9075a6c4a2b4840452733a4ab810e1.png

Resample the input signal with rate \(r\), \(y[n] = x(n \frac{T_s}{r})\)

Now, resample \(x[n]\) such that the output \(y[n]\) is equivalent to sampling the continuous-time \(x(t)\) at sample rate \(f_s = r/T_s\). This is equivalent to sampling \(x[n]\) at \(1/r\) fractional samples. This is accomplished, for arbitrary \(r\), with a Farrow arbitrary resampler.

In the sdr library, the Farrow arbitrary resampler is implemented in sdr.FarrowResampler.

def resample_signal(rate):
    farrow = sdr.FarrowResampler()
    y = farrow(x, rate)
    new_sample_rate = rate * sample_rate
    ty = np.arange(y.size) / new_sample_rate  # Time axis for output signal

    print(f"Input signal length: {x.size}")
    print(f"Output signal length: {y.size}")

    plt.figure(figsize=[10, 5])
    plt.plot(tx, x.real, linestyle="none", marker="o", fillstyle="none", label="Input (real)")
    plt.plot(tx, x.imag, linestyle="none", marker="o", fillstyle="none", label="Input (imag)")
    plt.gca().set_prop_cycle(None)
    plt.plot(ty, y.real, linestyle="none", marker=".", label="Output (real)")
    plt.plot(ty, y.imag, linestyle="none", marker=".", label="Output (imag)")
    plt.xlabel("Time (s)")
    plt.ylabel("Amplitude")
    plt.title(f"Original $x(t)$ and resampled signal $y(t)$, rate = {rate}")
    plt.legend()
    plt.grid(which="both", linestyle="--")
    plt.show()

Upsample the signal by an integer rate

When upsampling by 2, notice there are two output samples for every input sample.

resample_signal(2)
Input signal length: 100
Output signal length: 200
../../_images/3d43e09ec3f0c6c0fd39fba3b5b2c7eb3c4d63abd97c59cadf74566abb894da3.png

When upsampling by 4, notice there are four output samples for every input sample.

resample_signal(4)
Input signal length: 100
Output signal length: 400
../../_images/019bf47490a69cb3bb4dcbace9e1c1c1d4f43e5b56f0ba6371ccf798fb98fb48.png

Downsample the signal by an integer rate

When downsampling by 2, notice every other sample of the input appears at the output.

resample_signal(1 / 2)
Input signal length: 100
Output signal length: 50
../../_images/09a0e403f436ea775af0f56745d6af7abf4196c88ae996aae9fa17b58242d460.png

When downsampling by 4, notice every fourth sample of the input appears at the output.

resample_signal(1 / 4)
Input signal length: 100
Output signal length: 25
../../_images/8b652298396e7906e99d37b89f1986f87690972acac1f9de56e361618299eee9.png

Upsample by an irrational rate

When upsampling by \(\pi\), notice there are roughly three output samples for every input sample. However, these samples often do not align with the original input samples.

resample_signal(np.pi)
Input signal length: 100
Output signal length: 315
../../_images/00c027371d29e6aeff05a040159552f95c0c96b242046a38ddb11d15ae318d06.png

Downsample by an irrational rate

When downsampling by \(\pi\), notice there are roughly three input samples for every output sample. However, these samples often do not align with the original input samples.

resample_signal(1 / np.pi)
Input signal length: 100
Output signal length: 32
../../_images/d40472c9084911f997f9d889688dd1e4dd11ec3735e71d8f8e9eb21f4db6e1b1.png

Last update: Aug 10, 2023