Skip to content

Commit

Permalink
cleared fractiona res. history
Browse files Browse the repository at this point in the history
  • Loading branch information
prandoni committed May 3, 2020
1 parent 6f50ef3 commit 4c7464e
Show file tree
Hide file tree
Showing 2 changed files with 434 additions and 635 deletions.
328 changes: 328 additions & 0 deletions FractionalResampler/FractionalResampler.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# FIR Resampling\n",
"<div align=\"right\"><a href=\"https://people.epfl.ch/paolo.prandoni\">Paolo Prandoni</a>, <a href=\"https://www.epfl.ch/labs/lcav/\">LCAV, EPFL</a></div>\n",
"\n",
"In this notebook we will implement a simple fractional resampler that uses local Lagrange interpolation. The resampler can be used to perform any rational sampling rate change (but beware of aliasing when downsampling!)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import IPython\n",
"from scipy.io import wavfile"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Local Lagrange interpolation\n",
"\n",
"To perform resampling we will need to compute subsample values and, in order to compute subsample values without resorting to a full interpolation of the input signal, the idea is to fit a local polynomial interpolator arount the reference index $n$ and compute the value _of the polynomial_ at $n+\\tau$, where $|\\tau| < 1/2$. In the following figure, for instance, a quadratic interpolator is used:\n",
"\n",
"![title](interpolation.png)\n",
"\n",
"To fit the polynomial (whose degree, for reasons of symmetry, should be even) we can use Lagrange polynomials. In this case the approximation is simply: \n",
"\n",
"$$\n",
" x_L(n; \\tau) = \\sum_{k=-N}^{N} x[n + k] L_k^{(N)}(\\tau)\n",
"$$\n",
"\n",
"where $L_k^{(N)}(t)$ is the $k$-th Lagrange polynomial of order $2N$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Rational sampling rate change\n",
"\n",
"Given a nominal input sampling rate $F_i$ and an output sampling rate $F_o$, a fractional resampler generates $N_o$ output samples for every $N_i$ input samples where\n",
"\n",
"$$\n",
" \\frac{N_o}{N_i} = \\frac{F_o}{F_i}\n",
"$$\n",
"with $N_i, N_o$ coprime for maximum efficiency. So the first thing we need is a simple function that simplifies the ratio of sampling frequencies to its lowest terms. For this we will use Euclid's algorithm:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def simplify(A, B):\n",
" # Euclid's GCD algorithm\n",
" a = A\n",
" b = B\n",
" while a != b:\n",
" if a > b:\n",
" a = a - b\n",
" else:\n",
" b = b - a\n",
" return A // a, B // b"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can test the function on the usual CD to DVD sampling rate change and indeed we obtain the familiar 160/147 ratio:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"print(simplify(48000, 44100))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Fractional resampling"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A local fractional resampler works as follows:\n",
" * for each output index $m$, find the closest input index $n$, called the \"anchor\", so that the distance between input and output instants (in seconds) is less than one half of the input sampling period in magnitude; let $\\tau$ be this distance, normalized by the input sampling period; clearly $|\\tau| < 1/2$;\n",
" * perform a local Lagrange interpolation of the input around $n$;\n",
" * compute the value of the interpolation at $n +\\tau$.\n",
" \n",
"For a rational sampling rate change, the values of $\\tau$ repeat in a pattern every $N_o$ output point, so that the values of the Lagrange interpolation coefficients can be precomputed; for every block of $N_o$ output points we will use $N_i$ input points. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The method is best understood graphically: in the figures below, the top line shows the samples $x[n]$ in the original sequence, while the bottom line shows the resampled values $y[n]$. The red dotted lines indicate the intervals for each _input_ sample where $\\tau$ less than one half in magnitude; the blue arrows show which input sample is used as an anchor point in the computation of an output sample.\n",
"\n",
"Let's first consider downsampling, with a ratio $N_o/N_i = 4/5$; the operation generates 4 output samples for every 5 input samples; the anchor points are determined like so:\n",
"\n",
"![title](down.png)\n",
"\n",
"As apparent from the figure, since the output rate is less than the input rate, every once in a while an input sample is skipped."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In the following figure, the sgnal is upsampled with a ratio $N_o/N_i = 8/5$ generates 8 output samples for every 5 input samples; since the output rate is larger than the input rate, some of the input samples need to be reused.\n",
"\n",
"![title](up.png)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Mathematically, the anchor points are determined like so:\n",
"\n",
" * the output sample $y[m]$ occurs at time $t_m = m/F_o$\n",
" * the closest input sample will be $x[n]$ (occurring at time $t_n = n/F_i$) where $n$ is such that $|m/F_o - n/F_i| < 1/2$.\n",
" \n",
"The required value for $n$ can be found by setting \n",
"$$\n",
" \\left|\\mbox{frac}\\left(\\frac{m}{N_o} - \\frac{n}{N_i}\\right)\\right| < \\frac{1}{2}\n",
"$$\n",
"where `frac()` indicates the fractional part of a number. This yields\n",
"$$\n",
" n = \\mbox{round}\\left(m\\frac{N_i}{N_o}\\right);\n",
"$$\n",
"the fractional distance between $y[m]$ and $x[n]$ is given by the time difference normalized by the input's sampling period, that is\n",
"$$\n",
" \\tau = F_i\\left(\\frac{m}{F_o} - \\frac{n}{F_i}\\right) = m\\frac{N_i}{N_o} - \\mbox{round}\\left(m\\frac{N_i}{N_o}\\right).\n",
"$$\n",
"Note that $\\tau = 0$ every time $m$ is a multiple of $N_o$, which confirms the repetition pattern every $N_o$ output samples."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Setting up the filters\n",
"\n",
"In practice, we want the resampling to be driven by the input data, so we set up an array of $N_i$ filter _lists;_ for every new input sample we look at the next element in the list (modulo $N_i$) and produce as many output samples as there are filters in the list element.\n",
"\n",
"The following function sets up a set of $N_i$ quadratic interpolation filters for each anchor point:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def setup_filters(output_rate, input_rate):\n",
" No, Ni = simplify(output_rate, input_rate)\n",
" filterbank = [[] for _ in range(Ni)]\n",
" # while output index spans [0, No-1], the input spans [0, Ni-1]\n",
" for m in range(0, No):\n",
" anchor = int(m * Ni / No + 0.5) \n",
" tau = (m * Ni / No) - anchor\n",
" filterbank[anchor].append([\n",
" tau * (tau - 1) / 2, \n",
" (1 - tau) * (1 + tau), \n",
" tau * (tau + 1) / 2\n",
" ])\n",
" return filterbank"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can test with the examples in the figures above:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"setup_filters(4, 5)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"setup_filters(8, 5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### The final interpolator\n",
"We are now ready to write the full interpolation function:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def resample(output_rate, input_rate, x):\n",
" No, Ni = simplify(output_rate, input_rate)\n",
" filterbank = setup_filters(No, Ni)\n",
" \n",
" y = np.zeros(No * len(x)) // Ni \n",
" m = 0\n",
" for n, x_n in enumerate(x[1:-1]):\n",
" for fb in filterbank[n % Ni]:\n",
" y[m] = x[n-1] * fb[0] + x[n] * fb[1] + x[n+1] * fb[2]\n",
" m += 1\n",
" return y"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can now test the resampler on a simple sinusoid; we generate the sinusoid at 44.1 KHz and resample at 48KHz; the pitch should not change:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"x = np.cos(2 * np.pi * 440 / 44100 * np.arange(0, 44100))\n",
"IPython.display.Audio(x, rate=44100)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"y = resample(12000, 44100, x)\n",
"IPython.display.Audio(y, rate=12000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can now test the resampler on an audio file; note how aliasing appears when we downsample too much:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Fi, x = wavfile.read('oao.wav')\n",
"IPython.display.Audio(x, rate=Fi)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"y = resample(48000, Fi, x)\n",
"IPython.display.Audio(y, rate=48000)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"y = resample(8000, Fi, x)\n",
"IPython.display.Audio(y, rate=8000)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.4"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
741 changes: 106 additions & 635 deletions VoiceTransformer/VoiceTransformer.ipynb

Large diffs are not rendered by default.

0 comments on commit 4c7464e

Please sign in to comment.