Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
| Download

📚 The CoCalc Library - books, templates and other resources

Views: 96178
License: OTHER
Kernel: Python 2

ThinkDSP

This notebook contains solutions to exercises in Chapter 2: Harmonics

Copyright 2015 Allen Downey

License: Creative Commons Attribution 4.0 International

from __future__ import print_function, division %matplotlib inline import thinkdsp import thinkplot import numpy as np import math import warnings warnings.filterwarnings('ignore') from IPython.html.widgets import interact, interact_manual, fixed from IPython.html import widgets from IPython.display import display PI2 = 2 * math.pi

Exercise

A sawtooth signal has a waveform that ramps up linearly from -1 to 1, then drops to -1 and repeats. See http://en.wikipedia.org/wiki/Sawtooth_wave

Write a class called SawtoothSignal that extends Signal and provides evaluate to evaluate a sawtooth signal.

Compute the spectrum of a sawtooth wave. How does the harmonic structure compare to triangle and square waves?

Solution

My solution is basically a simplified version of TriangleSignal.

class SawtoothSignal(thinkdsp.Sinusoid): """Represents a sawtooth signal.""" def evaluate(self, ts): """Evaluates the signal at the given times. ts: float array of times returns: float wave array """ cycles = self.freq * ts + self.offset / PI2 frac, _ = np.modf(cycles) ys = thinkdsp.normalize(thinkdsp.unbias(frac), self.amp) return ys

Here's what it sounds like:

sawtooth = SawtoothSignal().make_wave(duration=0.5, framerate=40000) sawtooth.make_audio()

And here's what the spectrum looks like:

sawtooth.make_spectrum().plot()
Image in a Jupyter notebook

Compared to a square wave, the sawtooth drops off similarly, but it includes both even and odd harmonics. Notice that I had to cut the amplitude of the square wave to make them comparable.

sawtooth.make_spectrum().plot(color='gray') square = thinkdsp.SquareSignal(amp=0.5).make_wave(duration=0.5, framerate=40000) square.make_spectrum().plot()
Image in a Jupyter notebook

Compared to a triangle wave, the sawtooth doesn't drop off as fast.

sawtooth.make_spectrum().plot(color='gray') triangle = thinkdsp.TriangleSignal(amp=0.79).make_wave(duration=0.5, framerate=40000) triangle.make_spectrum().plot()
Image in a Jupyter notebook

Specifically, the harmonics of the triangle wave drop off in proportion to 1/f21/f^2, while the sawtooth drops off like 1/f1/f.

Exercise

Make a square signal at 1100 Hz and make a wave that samples it at 10000 frames per second. If you plot the spectrum, you can see that most of the harmonics are aliased. When you listen to the wave, can you hear the aliased harmonics?

Solution

Here's the square wave:

square = thinkdsp.SquareSignal(1100).make_wave(duration=0.5, framerate=10000)

Here's what the spectrum looks like:

square.make_spectrum().plot()
Image in a Jupyter notebook

The fundamental and the first harmonic are in the right place, but the second harmonic, which should be 5500 Hz, is aliased to 4500 Hz. The third, which should be 7700 Hz, is aliased to 2300 Hz, and so on.

When you listen to the wave, you can hear these aliased harmonics as a low tone.

square.make_audio()

The tone I hear most prominently is at 300 Hz. If you listen to this 300 Hz sine wave, you might hear what I mean.

thinkdsp.SinSignal(300).make_wave(duration=0.5, framerate=10000).make_audio()

Exercise

If you have a spectrum object, spectrum, and print the first few values of spectrum.fs, you’ll see that they start at zero. So spectrum.hs[0] is the magnitude of the component with frequency 0. But what does that mean?

Try this experiment:

  1. Make a triangle signal with frequency 440 and make a Wave with duration 0.01 seconds. Plot the waveform.

  2. Make a Spectrum object and print spectrum.hs[0]. What is the amplitude and phase of this component?

  3. Set spectrum.hs[0] = 100. Make a Wave from the modified Spectrum and plot it. What effect does this operation have on the waveform?

Solution

Here's the triangle wave:

triangle = thinkdsp.TriangleSignal().make_wave(duration=0.01) triangle.plot()
Image in a Jupyter notebook

The first element of the spectrum is a complex number close to zero.

spectrum = triangle.make_spectrum() spectrum.hs[0]
(1.0436096431476471e-14+0j)

If we add to the zero-frequency component, it has the effect of adding a vertical offset to the wave.

spectrum.hs[0] = 100 triangle.plot(color='gray') spectrum.make_wave().plot()
Image in a Jupyter notebook

The zero-frequency component is the total of all the values in the signal, as we'll see when we get into the details of the DFT. If the signal is unbiased, the zero-frequency component is 0. In the context of electrical signals, the zero-frequency term is called the DC offset; that is, a direct current offset added to an AC signal.

Exercise

Write a function that takes a Spectrum as a parameter and modifies it by dividing each element of hs by the corresponding frequency from fs. Test your function using one of the WAV files in the repository or any Wave object.

  1. Compute the Spectrum and plot it.

  2. Modify the Spectrum using your function and plot it again.

  3. Make a Wave from the modified Spectrum and listen to it. What effect does this operation have on the signal?

Solution

Here's my version of the function:

def filter_spectrum(spectrum): spectrum.hs /= spectrum.fs spectrum.hs[0] = 0

Here's a triangle wave:

wave = thinkdsp.TriangleSignal(freq=440).make_wave(duration=0.5) wave.make_audio()

Here's what the before and after look like. I scaled the after picture to make it visible on the same scale.

high = 10000 spectrum = wave.make_spectrum() spectrum.plot(high=high, color='gray') filter_spectrum(spectrum) spectrum.scale(440) spectrum.plot(high=high)
Image in a Jupyter notebook

The filter clobbers the harmonics, so it acts like a low pass filter.

Here's what it sounds like:

filtered = spectrum.make_wave() filtered.make_audio()

The triangle wave now sounds almost like a sine wave.

Exercise

The triangle and square waves have odd harmonics only; the sawtooth wave has both even and odd harmonics. The harmonics of the square and sawtooth waves drop off in proportion to 1/f1/f; the harmonics of the triangle wave drop off like 1/f21/f^2. Can you find a waveform that has even and odd harmonics that drop off like 1/f21/f^2?

Hint: There are two ways you could approach this: you could construct the signal you want by adding up sinusoids, or you could start with a signal that is similar to what you want and modify it.

Solution

One option is to start with a sawtooth wave, which has all of the harmonics we need:

freq = 500 signal = thinkdsp.SawtoothSignal(freq=freq) wave = signal.make_wave(duration=0.5, framerate=20000) wave.make_audio()

Here's what the spectrum looks like. The harmonics drop off like 1/f1/f.

spectrum = wave.make_spectrum() spectrum.plot()
Image in a Jupyter notebook

If we apply the filter we wrote in the previous exercise, we can make the harmonics drop off like 1/f21/f^2.

spectrum.plot(color='gray') filter_spectrum(spectrum) spectrum.scale(freq) spectrum.plot()
Image in a Jupyter notebook

Here's what it sounds like:

wave = spectrum.make_wave() wave.make_audio()

And here's what the waveform looks like.

wave.segment(duration=0.01).plot()
Image in a Jupyter notebook

It's an interesting shape, but not easy to see what its functional form is.

Another approach is to add up a series of cosine signals with the right frequencies and amplitudes.

freqs = np.arange(500, 9500, 500) amps = 1 / freqs**2 signal = sum(thinkdsp.CosSignal(freq, amp) for freq, amp in zip(freqs, amps)) signal
<thinkdsp.SumSignal instance at 0x7f1afa2f04d0>

Here's what the spectrum looks like:

spectrum = wave.make_spectrum() spectrum.plot()
Image in a Jupyter notebook

Here's what it sounds like:

wave = signal.make_wave(duration=0.5, framerate=20000) wave.make_audio()

And here's what the waveform looks like.

wave.segment(duration=0.01).plot()
Image in a Jupyter notebook

If those look to you like parabolas, you might be right. thinkdsp provides ParabolicSignal, which computes parabolic waveforms.

wave = thinkdsp.ParabolicSignal(freq=500).make_wave(duration=0.5, framerate=20000) wave.make_audio()

Here's what the waveform looks like:

wave.segment(duration=0.01).plot()
Image in a Jupyter notebook

A parabolic signal has even and odd harmonics which drop off like 1/f21/f^2:

spectrum = wave.make_spectrum() spectrum.plot()
Image in a Jupyter notebook