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()
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
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
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
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
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
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