diff --git a/PCP.ipynb b/PCP.ipynb index b815ffb..7035997 100644 --- a/PCP.ipynb +++ b/PCP.ipynb @@ -164,7 +164,7 @@ "\n", " 10\n", " Python Modules and Packages \n", - " Python modules; Python packages; libpcp; documentation; docstring \n", + " Python modules; Python packages; libpcp; documentation; docstring; sample solutions to exercises \n", " [html] \n", " [ipynb]\n", " \n", diff --git a/PCP_dft.ipynb b/PCP_dft.ipynb index 2fc9a79..84aa323 100644 --- a/PCP_dft.ipynb +++ b/PCP_dft.ipynb @@ -15,9 +15,8 @@ "source": [ "# Unit 9: Discrete Fourier Transform (DFT)\n", "\n", - "The Fourier transform is one of the most important tools for a wide range of applications in engineering and computer science. In this notebook, we have a look at a discrete variant of the Fourier transform known as Discrete Fourier Transform (DFT). Furthermore, we introduce and implement the fast Fourier transform (FFT), which is an efficient algorithm for computing the DFT. This notebook closely follows the [FMP Notebook on the Discrete Fourier Transform](https://www.audiolabs-erlangen.de/resources/MIR/FMP/C2/C2_ComplexNumbers.html).\n", - "\n", "\n", - "\n" + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " \n", + "
\n", + "

Overview and Learning Objectives

\n", + "\n", + " \n", + "The Fourier transform is one of the most important tools for a wide range of engineering and computer science applications. The general idea of Fourier analysis is to decompose a given signal into a weighted superposition of sinusoidal functions. Since these functions possess an explicit physical meaning regarding their frequencies, the decomposition is typically more accessible for subsequent processing steps than the original signal. Assuming that you are familiar with the Fourier transform and its applications in signal processing, we review in this unit the discrete variant of the Fourier transform known as Discrete Fourier Transform (DFT). We define the inner product that allows for comparing two vectors (e.g., discrete-time signals of finite length). The DFT can be thought of as comparing a given signal of finite length with a specific set of exponential signals (a complex variant of sinusoidal signals), each comparison yielding a complex-valued Fourier coefficient. Then, using suitable visualizations, we show how you can interpret the amplitudes and phases of these coefficients. Recall that one can express the DFT as a complex-valued square matrix. We show how separately plotting the real and imaginary parts leads to beautiful and insightful images. Applying a DFT boils down to computing matrix–vector product, which we implement via the standard NumPy function np.dot. Since the number of operations for computing a DFT via a simple matrix–vector product is quadratic in the input length, the runtime of this approach becomes problematic with increasing length. This issue is exactly where the fast Fourier transform (FFT) comes into the game. We present this famous divide-and-conquer algorithm and provide a Python implementation. Furthermore, we compare the runtime behavior between the FFT implementation and the naive DFT implementation. We will further deepen your understanding of the Fourier transform by considering further examples and visualization in the exercises. In Exercise 1, you will learn how to interpret and plot frequency indices in a physically meaningful way. In Exercise 2, we discuss the issue of loosing time information when applying the Fourier transform, which is the main motivation for the short-time Fourier transform. In Exercise 3, you will apply the DFT to a chirp signal, which yields another illustrative example of the DFT's properties. Finally, in Exercise 4, we will invite you to explore the relationship between the DFT and its inverse. Again, an overarching of this unit is to apply and deepen your Python programming skills within the context of a central topic for signal processing. \n", + " \n", + "
" ] }, { @@ -37,13 +49,19 @@ " \n", "## Inner Product\n", "\n", - "In this notebook, we consider [discrete-time (DT) signals](PCP_Signal.html) of finite length $N\\in\\mathbb{N}$, which we represent as vector $x=(x(0),x(1),...,x(N-1))^\\top\\in\\mathbb{R}^N$ with samples $x(n)\\in\\mathbb{R}^N$ for $n\\in[0:N-1]$. Note that we start indexing with the index $0$. A general concept for comparing two vectors (or signals) is the **inner product**. Given two vectors $x, y \\in \\mathbb{R}^N$, the inner product between $x$ and $y$ is defined as follows:\n", + "In this notebook, we consider [discrete-time (DT) signals](PCP_Signal.html) of finite length $N\\in\\mathbb{N}$, which we represent as vector \n", + "\n", + "$$\n", + "x=(x(0),x(1),...,x(N-1))^\\top\\in\\mathbb{R}^N\n", + "$$ \n", + "\n", + "with samples $x(n)\\in\\mathbb{R}^N$ for $n\\in[0:N-1]$. Note that $\\top$ indicates the transpose of a vector, thus converting a row vector into a column vector. Furthermore, note that we start indexing with the index $0$ (thus adapting our mathematical notation to Python conventions). A general concept for comparing two vectors (or signals) is the **inner product**. Given two vectors $x, y \\in \\mathbb{R}^N$, the inner product between $x$ and $y$ is defined as follows:\n", "\n", "$$ \n", "\\langle x | y \\rangle := \\sum_{n=0}^{N-1} x(n) y(n).\n", "$$\n", "\n", - "The absolute value of the inner product may be interpreted as a measure of similarity between $x$ and $y$: if $x$ and $y$ point to the same direction (i.e., $x$ and $y$ are similar), the inner product $|\\langle x | y \\rangle|$ is large. If $x$ and $y$ are orthogonal (i.e., $x$ and $y$ are dissimilar), the inner product $|\\langle x | y \\rangle|$ is zero.\n", + "The absolute value of the inner product may be interpreted as a measure of similarity between $x$ and $y$. If $x$ and $y$ are similar (i.e., if they point to more or less the same direction), the inner product $|\\langle x | y \\rangle|$ is large. If $x$ and $y$ are dissimilar (i.e., if $x$ and $y$ are more or less orthogonal to each other), the inner product $|\\langle x | y \\rangle|$ is close to zero.\n", "\n", "One can extend this concept to **complex-valued** vectors $x,y\\in\\mathrm{C}^N$, where the inner product is defined as \n", "\n", @@ -154,7 +172,7 @@ "* The signal $x_1$ is similar to itself, leading to a large value of $\\langle x_1 | x_1 \\rangle=40.0$.\n", "* The overall course of the signal $x_1$ strongly correlates with the sinusoid $x_2$, which is reflected by a relatively large value of $\\langle x_1 | x_2 \\rangle=29.9$.\n", "* There are some finer oscillations of $x_1$ that are captured by $x_3$, leading to a still noticeable value of $\\langle x_1 | x_3 \\rangle=14.7$. \n", - "* The two sinusoids $x_2$ and $x_3$ are not correlated at all, which is revealed by the value of $\\langle x_2 | x_3 \\rangle=0$. \n", + "* The two sinusoids $x_2$ and $x_3$ are more or less uncorrelated, which is revealed by the value of $\\langle x_2 | x_3 \\rangle\\approx 0$. \n", "\n", "In other words, the above comparison reveals that the signal $x_1$ has a strong signal component of $2~\\mathrm {Hz}$ (frequency of $x_2$) and $6~\\mathrm {Hz}$ (frequency of $x_3$). Measuring correlations between an arbitrary signal and sinusoids of different frequencies is exactly the idea of performing a Fourier (or spectral) analysis. " ] @@ -180,7 +198,7 @@ "\n", "This vector can be regarded as a [sampled version](PCP_signal.html) of the [exponential function](PCP_exp.html) of frequency $k/N$. Using inner products, the DFT can be expressed as\n", "\n", - "$$ X(k) = \\sum_{n=0}^{N-1} x(n) \\overline{\\mathbf{e}_k} = \\langle x | \\mathbf{e}_k \\rangle,$$\n", + "$$ X(k) = \\sum_{n=0}^{N-1} x(n) \\overline{\\mathbf{e}_k}(n) = \\langle x | \\mathbf{e}_k \\rangle,$$\n", "\n", "thus measuring the similarity between the signal $x$ and the sampled exponential functions $\\mathbf{e}_k$. The absolute value $|X(k)|$ indicates the degree of similarity between the signal $x$ and $\\mathbf{e}_k$. In the case that $x\\in \\mathbb{R}^N$ is a real-valued vector (which is typically the case for audio signals), we obtain:\n", "\n", @@ -698,11 +716,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "Runtime (ms) for N = 256 : DFT 12.14, FFT 0.01498, FFT_np 0.00001375\n", - "Runtime (ms) for N = 512 : DFT 33.29, FFT 0.02594, FFT_np 0.00001610\n", - "Runtime (ms) for N = 1024 : DFT 118.72, FFT 0.04788, FFT_np 0.00002885\n", - "Runtime (ms) for N = 2048 : DFT 421.35, FFT 0.09456, FFT_np 0.00004455\n", - "Runtime (ms) for N = 4096 : DFT 1543.14, FFT 0.18793, FFT_np 0.00010600\n" + "Runtime (ms) for N = 256 : DFT 23.99, FFT 0.01239, FFT_np 0.00001220\n", + "Runtime (ms) for N = 512 : DFT 31.06, FFT 0.02377, FFT_np 0.00001618\n", + "Runtime (ms) for N = 1024 : DFT 105.50, FFT 0.04329, FFT_np 0.00002411\n", + "Runtime (ms) for N = 2048 : DFT 381.55, FFT 0.08357, FFT_np 0.00004250\n", + "Runtime (ms) for N = 4096 : DFT 1441.13, FFT 0.15967, FFT_np 0.00008037\n" ] } ], diff --git a/PCP_module.ipynb b/PCP_module.ipynb index fd07ea1..016b7f2 100644 --- a/PCP_module.ipynb +++ b/PCP_module.ipynb @@ -15,9 +15,8 @@ "source": [ "# Unit 10: Python Modules and Packages\n", "\n", - "This notebook gives a short introduction to Python packages and Python modules. Furthermore, we explain the structure of libpcp, the Python package that accompanies the PCP notebooks.\n", - "\n", " " ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " \n", + "
\n", + "

Overview and Learning Objectives

\n", + "\n", + "This final unit of the PCP notebooks serves several purposes. First, we give a general introduction to Python modules and Python packages, which are fundamental concepts for organizing and making Python code available. Second, we introduce the Python package libpcp (that accompany the PCP notebooks) and use this package as a concrete example for illustrating the Python concepts. At the same time, this unit (together with Unit 1) also documents the technical backbone underlying the PCP notebooks. Last but not least, we will also uncover in this unit the secret of where one can find the sample solutions for all exercises. In summary, we hope that the PCP notebooks help students naturally transition from learning about Python programming and signal processing to beginning independent research following good scientific practices. Another main motivation of the notebooks is to indirectly guide students to employ open-source tools for software development and reproducible research. \n", + "\n", + "
" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/PCP_signal.ipynb b/PCP_signal.ipynb index b947009..05013b9 100644 --- a/PCP_signal.ipynb +++ b/PCP_signal.ipynb @@ -35,15 +35,7 @@ "
\n", "

Overview and Learning Objectives

\n", "\n", - "In technical fields such as engineering or computer science, a signal is a function that conveys information about the state or behavior of a physical system. For example, a signal may describe the time-varying sound pressure at some place, the motion of a particle through some space, the distribution of light on a screen representing an image, or the sequence of images as in the case of a video signal. In this unit, we consider the case of sound or audio signals, which may be represented graphically by a plot that shows the relative air pressure (with respect to a reference air pressure) over time. In some way, a sinusoid of given frequency may be thought of a prototype of such a signal. We start this unit by formally defining such a signal as a mathematical function. Interleaving theory with practice, we then define a Python function to generate a sinusoid as a first example, which you can look at by means of a plot and listen to via some audio playback. To digitally compute with signals, one typically needs to convert a continuous into a discrete time axis—a process referred to as sampling. While providing a Python function that performance some equidistant sampling, we also discuss the kind of information that may be lost in the sampling process. This leads us to topics related to aliasing and the sampling theorem. Finally, using sinusoidal signals as example, we cover the phenomena of interference. In the exercises, we take up and further deepen these aspects by studying the phenomenon known as beating and looking at further examples illustrating aliasing. We assume that you are mostly familiar with these fundamental concepts that typically taught in an introductory signal processing course. Besides a review of these concepts, another main learning objective of this unit is to illustrate how to generate and use multimedia objects (e.g., audio, image, and video objects) within a Jupyter session. \n", - "\n", - "This notebook is based on material from the FMP Notebook on Sampling and the FMP Notebook on Interference and Beating.\n", - "\n", - " \n", - "Fourier transform\n", - "Unit 8 \n", - "\n", - "These examples also\n", + "In technical fields such as engineering or computer science, a signal may be defined as a function that conveys information about the state or behavior of a physical system. For example, a signal may describe the time-varying sound pressure at some place, the motion of a particle through some space, the distribution of light on a screen representing an image, or the sequence of images as in the case of a video signal. In this unit, we consider the case of sound or audio signals, which may be represented graphically by a plot that shows the relative air pressure (with respect to a reference air pressure) over time. In some way, a sinusoid of a given frequency may be thought of as a prototype of an audio signal. We start this unit by formally defining a signal as a mathematical function. Interleaving theory with practice, we then provide a Python function to generate a sinusoid with different parameters (duration, amplitude, frequency, phase, sampling rate), which you can look at via a plot and listen to via audio playback. To digitally compute with signals, one typically needs to convert a signal given on a continuous-time axis into one given on a discrete-time axis—a process commonly referred to as sampling. While providing a Python function that performs some equidistant sampling, we also discuss the kind of information that may be lost in the sampling process. This leads us to topics related to aliasing and the sampling theorem. Finally, using sinusoidal signals as an example, we cover the phenomena of constructive and destructive interference. In the exercises, we further deepen these aspects by studying the phenomenon known as beating and by looking at further examples illustrating aliasing. We assume that you are familiar with these fundamental concepts that are typically taught in an introductory signal processing course. Besides reviewing these concepts, another main learning objective of this unit is to show you how to generate and use multimedia objects (e.g., audio, image, and video objects) within the Jupyter notebook framework. In particular, in fields such as multimedia engineering, the interaction with concrete multimedia objects (e.g., sound examples) will help you move from recalling and reciting theoretical concepts towards comprehension and application. Following similar educational considerations, you may find additional material in the FMP Notebooks for teaching and learning fundamentals of music processing. In particular, the FMP Notebook on Sampling and the FMP Notebook on Interference and Beating have served as basis for this unit. \n", " \n", "
" ] @@ -55,7 +47,7 @@ " \n", "## Signals\n", "\n", - "A **sound** is generated by a vibrating object such as the vocal cords of a singer, the string and soundboard of a violin, or the diaphragm of a kettledrum. In signal processing, such a sound is typically represented by a function or **signal** $f\\colon\\mathbb{R}\\to\\mathbb{R}$, which encodes the sound's air pressure changes over time. The signal is called **periodic** if high and low air pressures repeat in an alternating and regular fashion. In this case, the **period** of the signal is defined as the time required to complete a cycle. The **frequency**, measured in **Hertz** (Hz), is the reciprocal of the period. The prototype of such a periodic signal is a **sinusoid**, which is specified by its **frequency**, its **amplitude** (the peak deviation of the sinusoid from its mean), and its **phase** (determining where in its cycle the sinusoid is at time zero). In the following code cell, we provide a function for generating a sinusoid. " + "A **sound** is generated by a vibrating object such as the vocal cords of a singer, the string and soundboard of a violin, or the diaphragm of a kettledrum. In signal processing, such a sound is typically represented by a function or **signal** $f\\colon\\mathbb{R}\\to\\mathbb{R}$, which encodes the sound's air pressure changes over time. The signal is called **periodic** if its values repeat at regular intervals. Intuitively speaking, the **period** of the signal is defined as the time required to complete a cycle. The **frequency**, measured in **Hertz** (Hz), is the reciprocal of the period. The prototype of such a periodic signal is a **sinusoid**, which is specified by its **frequency**, its **amplitude** (the peak deviation of the sinusoid from its mean), and its **phase** (determining where in its cycle the sinusoid is at time zero). In the following code cell, we provide a function for generating a sinusoid. " ] }, { @@ -94,11 +86,11 @@ " Notebook: PCP_signal.ipynb\n", "\n", " Args:\n", - " dur: Duration in seconds (Default value = 1)\n", + " dur: Duration (in seconds) of sinusoid (Default value = 1)\n", " amp: Amplitude of sinusoid (Default value = 1)\n", - " freq: Frequency of sinusoid (Default value = 1)\n", - " phase: Phase of sinusoid (Default value = 0)\n", - " Fs: Sampling rate (Default value = 100)\n", + " freq: Frequency (in Hertz) of sinusoid (Default value = 1)\n", + " phase: Phase (relative to interval [0,1)) of sinusoid (Default value = 0)\n", + " Fs: Sampling rate (in samples per second) (Default value = 100)\n", "\n", " Returns:\n", " x: Signal\n", @@ -246,8 +238,8 @@ " Notebook: PCP_signal.ipynb\n", "\n", " Args:\n", - " dur: Duration (in seconds) of signal to be generated (Default value = 1)\n", - " Fs: Sampling rate (Default value = 100)\n", + " dur: Duration (in seconds) of signal (Default value = 1)\n", + " Fs: Sampling rate (in samples per second) (Default value = 100)\n", "\n", " Returns:\n", " x: Signal\n", @@ -470,7 +462,7 @@ " \n", "## Interference\n", "\n", - "In signal processing, **interference** occurs when a wave is superimposed with another wave of similar frequency. When a crest of one wave meets a crest of the other wave at some point, then the individual magnitudes add up for a certain period of time, which is known as **constructive interference**. Vice versa, when a crest of one wave meets a trough of the other wave, then the magnitudes cancel out for a certain period of time, which is known as **destructive interference**. We illustrate these phenomena in the following code cell. " + "In signal processing, **interference** occurs when a wave is superimposed with another wave that has the same or nearly the same frequency. When a crest of one wave meets a crest of the other wave at some point, then the individual magnitudes add up for a certain period of time, which is known as **constructive interference**. Vice versa, when a crest of one wave meets a trough of the other wave, then the magnitudes cancel out for a certain period of time, which is known as **destructive interference**. We illustrate these phenomena in the following code cell. " ] }, { @@ -889,7 +881,7 @@ " " ], "text/plain": [ - "" + "" ] }, "metadata": {}, diff --git a/libpcp/signal.py b/libpcp/signal.py index 68dc741..df2c576 100644 --- a/libpcp/signal.py +++ b/libpcp/signal.py @@ -16,11 +16,11 @@ def generate_sinusoid(dur=1, amp=1, freq=1, phase=0, Fs=100): Notebook: PCP_signal.ipynb Args: - dur: Duration in seconds (Default value = 1) + dur: Duration (in seconds) of sinusoid (Default value = 1) amp: Amplitude of sinusoid (Default value = 1) - freq: Frequency of sinusoid (Default value = 1) - phase: Phase of sinusoid (Default value = 0) - Fs: Sampling rate (Default value = 100) + freq: Frequency (in Hertz) of sinusoid (Default value = 1) + phase: Phase (relative to interval [0,1)) of sinusoid (Default value = 0) + Fs: Sampling rate (in samples per second) (Default value = 100) Returns: x: Signal @@ -38,8 +38,8 @@ def generate_example_signal(dur=1, Fs=100): Notebook: PCP_signal.ipynb Args: - dur: Duration (in seconds) of signal to be generated (Default value = 1) - Fs: Sampling rate (Default value = 100) + dur: Duration (in seconds) of signal (Default value = 1) + Fs: Sampling rate (in samples per second) (Default value = 100) Returns: x: Signal