This notebook contains solutions to exercises in Chapter 11: Modulation and sampling
Copyright 2015 Allen Downey
# Get thinkdsp.py
import os
if not os.path.exists('thinkdsp.py'):
!wget https://github.com/AllenDowney/ThinkDSP/raw/master/code/thinkdsp.py
import numpy as np
import matplotlib.pyplot as plt
from thinkdsp import decorate
As we have seen, if you sample a signal at too low a framerate, frequencies above the folding frequency get aliased. Once that happens, it is no longer possible to filter out these components, because they are indistinguishable from lower frequencies.
It is a good idea to filter out these frequencies before sampling; a low-pass filter used for this purpose is called an ``anti-aliasing filter''.
Returning to the drum solo example, apply a low-pass filter before sampling, then apply the low-pass filter again to remove the spectral copies introduced by sampling. The result should be identical to the filtered signal.
Solution: I'll load the drum solo again.
if not os.path.exists('263868__kevcio__amen-break-a-160-bpm.wav'):
!wget https://github.com/AllenDowney/ThinkDSP/raw/master/code/263868__kevcio__amen-break-a-160-bpm.wav
from thinkdsp import read_wave
wave = read_wave('263868__kevcio__amen-break-a-160-bpm.wav')
wave.normalize()
wave.plot()
This signal is sampled at 44100 Hz. Here's what it sounds like.
wave.make_audio()
And here's the spectrum:
spectrum = wave.make_spectrum(full=True)
spectrum.plot()
I'll reduce the sampling rate by a factor of 3 (but you can change this to try other values):
factor = 3
framerate = wave.framerate / factor
cutoff = framerate / 2 - 1
Before sampling we apply an anti-aliasing filter to remove frequencies above the new folding frequency, which is framerate/2
:
spectrum.low_pass(cutoff)
spectrum.plot()
Here's what it sounds like after filtering (still pretty good).
filtered = spectrum.make_wave()
filtered.make_audio()
Here's the function that simulates the sampling process:
from thinkdsp import Wave
def sample(wave, factor):
"""Simulates sampling of a wave.
wave: Wave object
factor: ratio of the new framerate to the original
"""
ys = np.zeros(len(wave))
ys[::factor] = np.real(wave.ys[::factor])
return Wave(ys, framerate=wave.framerate)
The result contains copies of the spectrum near 20 kHz; they are not very noticeable:
sampled = sample(filtered, factor)
sampled.make_audio()
But they show up when we plot the spectrum:
sampled_spectrum = sampled.make_spectrum(full=True)
sampled_spectrum.plot()
We can get rid of the spectral copies by applying the anti-aliasing filter again:
sampled_spectrum.low_pass(cutoff)
sampled_spectrum.plot()
We just lost half the energy in the spectrum, but we can scale the result to get it back:
sampled_spectrum.scale(factor)
spectrum.plot()
sampled_spectrum.plot()
Now the difference between the spectrum before and after sampling should be small.
spectrum.max_diff(sampled_spectrum)
1.8189894035458565e-12
After filtering and scaling, we can convert back to a wave:
interpolated = sampled_spectrum.make_wave()
interpolated.make_audio()
And the difference between the interpolated wave and the filtered wave should be small.
filtered.plot()
interpolated.plot()
filtered.max_diff(interpolated)
5.56290642113787e-16