music/filters.ipynb

253 lines
52 KiB
Text
Raw Normal View History

2018-03-27 07:21:50 -07:00
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Filters"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"import math\n",
"import matplotlib\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from signals.domain import Time\n",
"from signals import waves\n",
"\n",
"# Show plots inline.\n",
"%matplotlib inline\n",
"\n",
"# Range of frequencies to plot.\n",
"FreqRange = (0, 20000)\n",
"# Plot resolution.\n",
"Steps = 200"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The simplest low-pass filter is given by this difference equation: $$y(n) = x(n)+x(n-1)$$ where $x(n)$ is the input amplitude at time (or sample) $n$ and $y(n)$ is the output amplitude at time $n$.\n",
"\n",
"You can also write this in terms of time instead of sample indices this way: $$y(nT)=x(nT)+x[(n-1)T]$$\n",
"for $n=0,1,2,...$ where $T$ is the sampling interval in seconds. Usually this is omitted by setting $T=1$."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"def simple_lowpass(x, x0=0):\n",
" '''Simplest possible low-pass filter.'''\n",
" y = np.zeros(x.size)\n",
" y[0] = x[0] + x0\n",
" for i in range(1, x.size):\n",
" y[i] = x[i] + x[i - 1]\n",
" return y"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Frequency Response\n",
"\n",
"It's very useful to know how a filter respond across a frequency spectrum $x(\\cdot)$.\n",
"\n",
"If we filter a sinusoid at each frequency separately, this is called _sine-wave analysis_."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"scrolled": false
},
"outputs": [
{
"data": {
"text/plain": [
"[<matplotlib.lines.Line2D at 0x10721d240>]"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"sec = 1.0\n",
"time = Time(sec=sec, samples=20)\n",
"sine = waves.sine(time.domain, freq=time.rate / 4)\n",
"filtered = simple_lowpass(sine)\n",
"plt.plot(sine)\n",
"plt.plot(filtered)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The ratio of the amplitude of the output to the amplitude of the input is called the _gain_ of the filter.\n",
"\n",
"_NOTE:_ It's hard to see here because I don't know how to reconstruct the continuous signal from the sampled output signal. I should look up how to do this.\n",
"\n",
"This is called the _amplitude response_."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.0000000000000029\n"
]
}
],
"source": [
"def gain(in_signal, out_signal):\n",
" return np.max(out_signal) / np.max(in_signal)\n",
"print(gain(sine, filtered))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can do this for every frequency between 0 and $f_s / 2$ and graph the result."
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/eryn/Library/Python/3.6/lib/python/site-packages/ipykernel_launcher.py:6: RuntimeWarning: invalid value encountered in double_scalars\n",
" \n"
]
},
{
"data": {
"text/plain": [
"[<matplotlib.lines.Line2D at 0x1077cb2b0>]"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"frequencies = np.linspace(0, time.samples // 2, num=40, endpoint=False)\n",
"freq_response = np.empty(frequencies.shape[0])\n",
"for f in range(0, frequencies.shape[0]):\n",
" sine = waves.sine(time.domain, freq=frequencies[f])\n",
" filtered = simple_lowpass(sine)\n",
" freq_response[f] = np.max(filtered) / np.max(sine)\n",
"\n",
"plt.plot(freq_response)\n",
"\n",
"# TODO: Deal with the nan"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Notice here that a the lowest frequency we had a gain boost of 2, which we can express in decibels with the following formula:"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"6.020599913279624"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"20 * np.log10(2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In other words, the filter's gain is about 6 dB."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"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.6.4"
}
},
"nbformat": 4,
"nbformat_minor": 2
}