ThinkDSP

by Allen Downey (think-dsp.com)

This notebook contains examples and demos for a SciPy 2015 talk.

In [1]:
from __future__ import print_function, division

import thinkdsp
import thinkplot

import numpy

%matplotlib inline

A Signal represents a function that can be evaluated at an point in time.

In [2]:
cos_sig = thinkdsp.CosSignal(freq=440)

A cosine signal at 440 Hz has a period of 2.3 ms.

In [3]:
cos_sig.plot()
thinkplot.config(xlabel='time (s)', legend=False)

make_wave samples the signal at equally-space time steps.

In [4]:
wave = cos_sig.make_wave(duration=0.5, framerate=11025)

make_audio creates a widget that plays the Wave.

In [5]:
wave.apodize()
wave.make_audio()
Out[5]:

make_spectrum returns a Spectrum object.

In [6]:
spectrum = wave.make_spectrum()

A cosine wave contains only one frequency component (no harmonics).

In [7]:
spectrum.plot()
thinkplot.config(xlabel='frequency (Hz)', legend=False)

A SawTooth signal has a more complex harmonic structure.

In [8]:
saw_sig = thinkdsp.SawtoothSignal(freq=440)
saw_sig.plot()

Here's what it sounds like:

In [9]:
saw_wave = saw_sig.make_wave(duration=0.5)
saw_wave.make_audio()
Out[9]:

And here's what the spectrum looks like:

In [10]:
saw_wave.make_spectrum().plot()

Here's a short violin performance from jcveliz on freesound.org:

In [11]:
violin = thinkdsp.read_wave('92002__jcveliz__violin-origional.wav')
violin.make_audio()
Out[11]:

The spectrogram shows the spectrum over time:

In [12]:
spectrogram = violin.make_spectrogram(seg_length=1024)
spectrogram.plot(high=5000)

We can select a segment where the pitch is constant:

In [13]:
start = 1.2
duration = 0.6
segment = violin.segment(start, duration)

And compute the spectrum of the segment:

In [14]:
spectrum = segment.make_spectrum()
spectrum.plot()

The dominant and fundamental peak is at 438.3 Hz, which is a slightly flat A4 (about 7 cents).

In [15]:
spectrum.peaks()[:5]
Out[15]:
[(2052.3878454763076, 438.33333333333337),
 (1504.1231272792359, 876.66666666666674),
 (1313.4058092162209, 878.33333333333337),
 (1024.7130064064424, 2193.3333333333335),
 (809.76238398486601, 2195.0)]

As an aside, you can use the spectrogram to help extract the Parson's code and then identify the song.

Parson's code: DUUDDUURDR

Send it off to http://www.musipedia.org

A chirp is a signal whose frequency varies continuously over time (like a trombone).

In [16]:
import math
PI2 = 2 * math.pi

class SawtoothChirp(thinkdsp.Chirp):
    """Represents a sawtooth signal with varying frequency."""

    def _evaluate(self, ts, freqs):
        """Helper function that evaluates the signal.

        ts: float array of times
        freqs: float array of frequencies during each interval
        """
        dts = numpy.diff(ts)
        dps = PI2 * freqs * dts
        phases = numpy.cumsum(dps)
        phases = numpy.insert(phases, 0, 0)
        cycles = phases / PI2
        frac, _ = numpy.modf(cycles)
        ys = thinkdsp.normalize(thinkdsp.unbias(frac), self.amp)
        return ys

Here's what it looks like:

In [17]:
signal = SawtoothChirp(start=220, end=880)
wave = signal.make_wave(duration=2, framerate=10000)
segment = wave.segment(duration=0.06)
segment.plot()

Here's the spectrogram.

In [18]:
spectrogram = wave.make_spectrogram(1024)
spectrogram.plot()
thinkplot.config(xlabel='time (s)', 
                 ylabel='frequency (Hz)', 
                 legend=False)

What do you think it sounds like?

In [19]:
wave.apodize()
wave.make_audio()
Out[19]:

Up next is one of the coolest examples in Think DSP. It uses LTI system theory to characterize the acoustics of a recording space and simulate the effect this space would have on the sound of a violin performance.

I'll start with a recording of a gunshot:

In [20]:
response = thinkdsp.read_wave('180960__kleeb__gunshot.wav')

start = 0.12
response = response.segment(start=start)
response.shift(-start)

response.normalize()
response.plot()
thinkplot.config(xlabel='time (s)', 
                 ylabel='amplitude', 
                 ylim=[-1.05, 1.05], 
                 legend=False)

If you play this recording, you can hear the initial shot and several seconds of echos.

In [21]:
response.make_audio()
Out[21]: