CH03 DSP Signal
CH03 DSP Signal
Non-periodic signals
The signals we have worked with so far are periodic, which means that they
repeat forever. It also means that the frequency components they contain do
not change over time. In this chapter, we consider non-periodic signals, whose
frequency components do change over time. In other words, pretty much all
sound signals.
This chapter also presents spectrograms, a common way to visualize non-
periodic signals.
The code for this chapter is in chap03.ipynb, which is in the repository for
this book (see Section 0.2). You can also view it at http://tinyurl.com/
thinkdsp03.
���
���
���
���
���
����� ����� ����� ����� ����� ����� ����������������������������������� �����������������������������������
��������
Figure 3.1: Chirp waveform near the beginning, middle, and end.
class Chirp(Signal):
phases = np.cumsum(dphis)
phases = np.insert(phases, 0, 0)
ys = self.amp * np.cos(phases)
return ys
np.diff computes the difference between adjacent elements of ts, returning
the length of each interval in seconds. If the elements of ts are equally spaced,
the dts are all the same.
The next step is to figure out how much the phase changes during each interval.
In Section 1.7 we saw that when frequency is constant, the phase, φ, increases
linearly over time:
φ = 2πf t
When frequency is a function of time, the change in phase during a short time
interval, Δt is:
Δφ = 2πf (t)Δt
In Python, since freqs contains f (t) and dts contains the time intervals, we
can write
dphis = PI2 * freqs * dts
Now, since dphis contains the changes in phase, we can get the total phase at
each timestep by adding up the changes:
phases = np.cumsum(dphis)
phases = np.insert(phases, 0, 0)
np.cumsum computes the cumulative sum, which is almost what we want, but
it doesn’t start at 0. So I use np.insert to add a 0 at the beginning.
The result is a NumPy array where the ith element contains the sum of the
first i terms from dphis; that is, the total phase at the end of the ith interval.
Finally, np.cos computes the amplitude of the wave as a function of phase
(remember that phase is expressed in radians).
If you know calculus, you might notice that the limit as Δt gets small is
dφ = 2πf (t)dt
���
���
���
���
���������
���
���
���
���
��
�
� ��� ��� ��� ��� ��� ��� ���
��������������
3.4 Spectrogram
To recover the relationship between frequency and time, we can break the chirp
into segments and plot the spectrum of each segment. The result is called a
short-time Fourier transform (STFT).
There are several ways to visualize a STFT, but the most common is a spec-
trogram, which shows time on the x-axis and frequency on the y-axis. Each
column in the spectrogram shows the spectrum of a short segment, using color
or grayscale to represent amplitude.
28 Chapter 3. Non-periodic signals
���
���
���
��������������
���
���
���
���
�
��� ��� ��� ��� ��� ���
��������
r/2
n/2
which is r/n.
Ideally we would like time resolution to be small, so we can see rapid changes
in frequency. And we would like frequency resolution to be small so we can
see small changes in frequency. But you can’t have both. Notice that time
resolution, n/r, is the inverse of frequency resolution, r/n. So if one gets
smaller, the other gets bigger.
For example, if you double the segment length, you cut frequency resolution
in half (which is good), but you double time resolution (which is bad). Even
increasing the framerate doesn’t help. You get more samples, but the range
of frequencies increases at the same time.
This tradeoff is called the Gabor limit and it is a fundamental limitation of
this kind of time-frequency analysis.
3.6 Leakage
In order to explain how make_spectrogram works, I have to explain window-
ing; and in order to explain windowing, I have to show you the problem it is
meant to address, which is leakage.
The Discrete Fourier Transform (DFT), which we use to compute Spectrums,
treats waves as if they are periodic; that is, it assumes that the finite segment
it operates on is a complete period from an infinite signal that repeats over all
time. In practice, this assumption is often false, which creates problems.
One common problem is discontinuity at the beginning and end of the segment.
Because DFT assumes that the signal is periodic, it implicitly connects the
end of the segment back to the beginning to make a loop. If the end does
not connect smoothly to the beginning, the discontinuity creates additional
frequency components in the segment that are not in the signal.
30 Chapter 3. Non-periodic signals
��� ���
��� ���
���
���
���
��� ���
���
���
���
��� ��
�� ��
� � �
� ��� ��� ��� ��� � ��� ��� ��� ��� � ��� ��� ��� ���
��������������
As an example, let’s start with a sine signal that contains only one frequency
component at 440 Hz.
signal = thinkdsp.SinSignal(freq=440)
If we select a segment that happens to be an integer multiple of the period, the
end of the segment connects smoothly with the beginning, and DFT behaves
well.
duration = signal.period * 30
wave = signal.make_wave(duration)
spectrum = wave.make_spectrum()
Figure 3.4 (left) shows the result. As expected, there is a single peak at 440
Hz.
But if the duration is not a multiple of the period, bad things happen. With
duration = signal.period * 30.25, the signal starts at 0 and ends at 1.
Figure 3.4 (middle) shows the spectrum of this segment. Again, the peak is
at 440 Hz, but now there are additional components spread out from 240 to
640 Hz. This spread is called spectral leakage, because some of the energy
that is actually at the fundamental frequency leaks into other frequencies.
In this example, leakage happens because we are using DFT on a segment that
becomes discontinuous when we treat it as periodic.
3.7. Windowing 31
���
���
���
���
���
����� ����� ����� ����� �����
���
���
���
���
���
����� ����� ����� ����� �����
���
���
���
���
���
����� ����� ����� ����� �����
��������
3.7 Windowing
We can reduce leakage by smoothing out the discontinuity between the begin-
ning and end of the segment, and one way to do that is windowing.
Figure 3.5 (middle) shows a “Hamming window”, one of the more common
window functions. No window function is perfect, but some can be shown
to be optimal for different applications, and Hamming is a good, all-purpose
window.
Figure 3.5 (bottom) shows the result of multiplying the window by the original
signal. Where the window is close to 1, the signal is unchanged. Where the
window is close to 0, the signal is attenuated. Because the window tapers at
both ends, the end of the segment connects smoothly to the beginning.
Figure 3.4 (right) shows the spectrum of the windowed signal. Windowing has
reduced leakage substantially, but not completely.
32 Chapter 3. Non-periodic signals
���
���
���
���
���
���
� ��� ��� ��� ��� ��� ��� ��� ���
��������
Here’s what the code looks like. Wave provides window, which applies a Ham-
ming window:
#class Wave:
def window(self, window):
self.ys *= window
And NumPy provides hamming, which computes a Hamming window with a
given length:
window = np.hamming(len(wave))
wave.window(window)
NumPy provides functions to compute other window functions, including
bartlett, blackman, hanning, and kaiser. One of the exercises at the end
of this chapter asks you to experiment with these other windows.
spec_map = {}
3.8. Implementing spectrograms 33
t = (segment.start + segment.end) / 2
spec_map[t] = segment.make_spectrum()
i += step
j += step
3.9 Exercises
Solutions to these exercises are in chap03soln.ipynb.
Exercise 3.1 Run and listen to the examples in chap03.ipynb, which is
in the repository for this book, and also available at http://tinyurl.com/
thinkdsp03.
In the leakage example, try replacing the Hamming window with one of
the other windows provided by NumPy, and see what effect they have
on leakage. See http://docs.scipy.org/doc/numpy/reference/routines.
window.html
Exercise 3.2 Write a class called SawtoothChirp that extends Chirp and
overrides evaluate to generate a sawtooth waveform with frequency that in-
creases (or decreases) linearly.
Hint: combine the evaluate functions from Chirp and SawtoothSignal.
Draw a sketch of what you think the spectrogram of this signal looks like, and
then plot it. The effect of aliasing should be visually apparent, and if you
listen carefully, you can hear it.
Exercise 3.3 Make a sawtooth chirp that sweeps from 2500 to 3000 Hz, then
use it to make a wave with duration 1 s and framerate 20 kHz. Draw a sketch
of what you think the spectrum will look like. Then plot the spectrum and
see if you got it right.
Exercise 3.4 In musical terminology, a “glissando” is a note that slides from
one pitch to another, so it is similar to a chirp.
Find or make a recording of a glissando and plot a spectrogram of the first
few seconds. One suggestion: George Gershwin’s Rhapsody in Blue starts with
a famous clarinet glissando, which you can download from http://archive.
org/details/rhapblue11924.
Exercise 3.5 A trombone player can play a glissando by extending the trom-
bone slide while blowing continuously. As the slide extends, the total length
of the tube gets longer, and the resulting pitch is inversely proportional to
length.
Assuming that the player moves the slide at a constant speed, how does fre-
quency vary with time?
Write a class called TromboneGliss that extends Chirp and provides
evaluate. Make a wave that simulates a trombone glissando from C3 up
to F3 and back down to C3. C3 is 262 Hz; F3 is 349 Hz.