import numpy as np
Exercise: Generate five cosine tones with the following parameters:
Amplitude | Frequency/Hz | Phase/Degree | |
---|---|---|---|
$x_1$ | 1.0 | 200 | 0 |
$x_2$ | 0.75 | 400 | 0 |
$x_3$ | 0.5 | 600 | 90 |
$x_4$ | 0.25 | 800 | 90 |
$x_5$ | 0.125 | 1000 | -90 |
All five signals should have a duration of 1 second and a sampling rate of 44.1 kHz. Hint: Try to exploit broadcasting.
Exercise: Generate the sum signal $x_6 = x_1 + x_2 + x_3 + x_4 + x_5$.
Exercise: Plot all 6 signals at once.
Exercise: Listen to the individual tones and to the sum signal. Use the function tools.fade()
from tools.py to avoid clicks at the beginning and/or the end. Watch for the volume of the sum signal, reduce the level if necessary. What happens if you don’t reduce the sound level?
Note that limiting the range from -1 to 1 is only relevant once you play back a signal over the sound card or save it as a sound file. For internal calculations you can of course use the full double precision (a.k.a. 'float64') range.
Exercise: Write a function named naive_fourier_transform()
, that calculates the Discrete Fourier Transform $X[k]$ of a one-dimensional array $x[n]$ using the equation
where $N$ is the number of samples of $x$ and $k$ is the frequency index with $0 \le k \lt N$.
Exercise: Call your function with $x_1$ as argument.
The calculation will take some time; in the meantime, estimate how many multiplications and additions are calculated and how often the exponential function is called.
If your calculation from above still isn't finished, stop it with the menu item "Kernel" $\to$ "Interrupt" (or by clicking the "stop" icon).
Call the function again, but this time only using the first 1000 values of $x_1$.
Exercise: Think about how to implement the same function as a matrix multiplication. Would that need less processing power? What about the memory requirements?
Exercise: Now, calculate the discrete Fourier transform of $x_1$ using the function numpy.fft.fft().
That should be faster, right?
Exercise: What algorithmic complexity does the FFT algorithm have?
If you only computed the first 1000 values above, do the same here.
Exercise: Calculate the difference of the results of naive_fourier_transform()
and numpy.fft.fft()
.
What is the maximum absolute error?
To assess the error, consider the maximum value of the result.
If the maximum error (in this concrete example) is in the order of magnitude of around $10^{-10}$ to $10^{-8}$, everything is OK (what you are seeing are rounding errors). If it is significantly larger, something is wrong. If it is significantly smaller, as well.
Exercise: How large is the error in decibels relative to the absolute maximum?
Exercise: In comparison, how large is the theoretically maximum signal-to-noise ratio (in decibel) of a linearly quantized 16-bit signal? Does this depend on the sampling frequency?
Now that you have checked (admittedly not very thoroughly) the correctness of the
function numpy.fft.fft()
and experienced its efficiency, you should also use it.
Exercise: Apply the function to the signals $x_1$ to $x_6$ and plot the results. Plot $x_1$ to $x_5$ in a common plot and $x_6$ separately. Don't forget the frequency axis!
You should have noticed that the result is complex - it is called the complex spectrum. The DFT of a real signal is in general complex, by the way. In order to recognize something in the plots, you should plot magnitude and phase of the complex spectrum separately.
Exercise: Calculate the inverse FFT (numpy.fft.ifft()) of the FFT of $x_1$. Is there a difference between the result and $x_1$?
Exercise: Write a function named plot_fft()
, that calculates the DFT of a signal and plots a figure with two subplots with magnitude and phase (in degrees).
Pass the sampling frequency to the function, in order to be able to label the x-axis in Hertz.
One property of the DFT is, that the transform of a real signal is symmetrical.
Exercise: What does this mean with regards to the plots?
Especially to the magnitude plot?
How can we "simplify" our plots with that?
Use this simplification in a new version of your function plot_fft()
.
If you don't remember the symmetries, you can look them up in any book about signal processing (or on Wikipedia).
Since half of the data is redundant, we wouldn't even have to calculate it. NumPy provides the functions numpy.fft.rfft() and numpy.fft.irfft() to provide only the non-redundant result.
Bonus Exercise: Make yet another variant of your function plot_fft()
that uses the "real" FFT functions.
Exercise: Plot all signals from the table at the top of the page in the time domain (with matplotlib.pyplot.plot()
) and in the frequency domain (with your function plot_fft()
).
Which of the given parameters can you recognize in which plots? Which are not visible?
If you had problems solving some of the exercises, don't despair! Have a look at the example solutions.