sdr.sample_rate_offset(x: ArrayLike, offset: ArrayLike, offset_rate: float = 0.0, sample_rate: float = 1.0) NDArray

Applies a sample rate offset to the time-domain signal \(x[n]\).

Parameters:
x: ArrayLike

The time-domain signal \(x[n]\) to which the sample rate offset is applied.

offset: ArrayLike

The sample rate offset \(\Delta f_s = f_{s,\text{new}} - f_{s}\) in samples/s. The offset can be a scalar or an array of the same size as \(x[n]\).

offset_rate: float = 0.0

The sample rate offset rate \(\Delta^2 f_s / \Delta t\) in samples/s^2.

sample_rate: float = 1.0

The current sample rate \(f_s\) in samples/s.

Returns:

The signal \(x[n]\) with sample rate offset applied.

Notes

The sample rate offset is applied using a Farrow resampler. The resampling rate is calculated as follows.

\[ \text{rate} = \frac{f_{s,\text{new}}}{f_s} = \frac{f_s + \Delta f_s + \frac{\Delta f_s}{\Delta t}}{f_s} \]

Examples

Consider a reference signal of slope 1. The value of the signal is equivalent to the input sample number. Then resample this signal with various constant sample rate offsets.

In [1]: x = np.arange(0, 20)

In [2]: plt.figure(); \
   ...: sdr.plot.time_domain(sdr.sample_rate_offset(x, 0), marker=".", label=0); \
   ...: sdr.plot.time_domain(sdr.sample_rate_offset(x, -0.2), marker=".", label=-0.2); \
   ...: sdr.plot.time_domain(sdr.sample_rate_offset(x, 0.2), marker=".", label=0.2); \
   ...: plt.xlabel("Output sample, $n$"); \
   ...: plt.ylabel("Input sample, $n$"); \
   ...: plt.legend(title="Offset"); \
   ...: plt.title("Constant sample rate offset");
   ...: 
../../_images/sdr_sample_rate_offset_1.svg

The same can be done with a constant sample rate offset rate.

In [3]: plt.figure(); \
   ...: sdr.plot.time_domain(sdr.sample_rate_offset(x, 0), marker=".", label=0); \
   ...: sdr.plot.time_domain(sdr.sample_rate_offset(x, 0.2, offset_rate=0 / x.size), marker=".", label="0.2 + 0/n"); \
   ...: sdr.plot.time_domain(sdr.sample_rate_offset(x, 0.2, offset_rate=-0.2 / x.size), marker=".", label="0.2 + -0.2/n"); \
   ...: sdr.plot.time_domain(sdr.sample_rate_offset(x, 0.2, offset_rate=-0.4 / x.size), marker=".", label="0.2 + -0.4/n"); \
   ...: sdr.plot.time_domain(sdr.sample_rate_offset(x, 0.2, offset_rate=-0.8 / x.size), marker=".", label="0.2 + -0.8/n"); \
   ...: plt.xlabel("Output sample, $n$"); \
   ...: plt.ylabel("Input sample, $n$"); \
   ...: plt.legend(title="Offset"); \
   ...: plt.title("Constant sample rate offset rate");
   ...: 
../../_images/sdr_sample_rate_offset_2.svg

Create a QPSK reference signal.

In [4]: psk = sdr.PSK(4, phase_offset=45); \
   ...: s = np.random.randint(0, psk.order, 1_000); \
   ...: x = psk.map_symbols(s)
   ...: 

Add 10 ppm of sample rate offset.

In [5]: y = sdr.sample_rate_offset(x, 10e-6)

In [6]: plt.figure(); \
   ...: sdr.plot.constellation(x, label="$x[n]$", zorder=2); \
   ...: sdr.plot.constellation(y, label="$y[n]$", zorder=1); \
   ...: plt.title("10 ppm sample rate offset");
   ...: 
../../_images/sdr_sample_rate_offset_3.svg

Add 100 ppm of sample rate offset.

In [7]: y = sdr.sample_rate_offset(x, 100e-6)

In [8]: plt.figure(); \
   ...: sdr.plot.constellation(x, label="$x[n]$", zorder=2); \
   ...: sdr.plot.constellation(y, label="$y[n]$", zorder=1); \
   ...: plt.title("100 ppm sample rate offset");
   ...: 
../../_images/sdr_sample_rate_offset_4.svg