Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 50 additions & 46 deletions code/thinkdsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def __init__(self, filename='sound.wav', framerate=11025):
self.fp.setnchannels(self.nchannels)
self.fp.setsampwidth(self.sampwidth)
self.fp.setframerate(self.framerate)

def write(self, wave):
"""Writes a wave.

Expand Down Expand Up @@ -92,14 +92,14 @@ def read_wave(filename='sound.wav'):
nframes = fp.getnframes()
sampwidth = fp.getsampwidth()
framerate = fp.getframerate()

z_str = fp.readframes(nframes)

fp.close()

dtype_map = {1:numpy.int8, 2:numpy.int16}
assert sampwidth in dtype_map

ys = numpy.fromstring(z_str, dtype=dtype_map[sampwidth])
wave = Wave(ys, framerate)
return wave
Expand All @@ -123,23 +123,23 @@ class _SpectrumParent(object):
@property
def max_freq(self):
return self.framerate / 2.0

@property
def freq_res(self):
return self.max_freq / (len(self.fs) - 1)

def plot(self, low=0, high=None, **options):
"""Plots amplitude vs frequency.

low: int index to start at
low: int index to start at
high: int index to end at
"""
thinkplot.plot(self.fs[low:high], self.amps[low:high], **options)

def plot_power(self, low=0, high=None, **options):
"""Plots power vs frequency.

low: int index to start at
low: int index to start at
high: int index to end at
"""
thinkplot.plot(self.fs[low:high], self.power[low:high], **options)
Expand Down Expand Up @@ -183,7 +183,7 @@ def __add__(self, other):
return Spectrum(hs, self.framerate)

__radd__ = __add__


@property
def real(self):
Expand Down Expand Up @@ -271,7 +271,7 @@ def make_wave(self):

class IntegratedSpectrum(object):
"""Represents the integral of a spectrum."""

def __init__(self, cs, fs):
"""Initializes an integrated spectrum:

Expand All @@ -284,7 +284,7 @@ def __init__(self, cs, fs):
def plot_power(self, low=0, high=None, expo=False, **options):
"""Plots the integrated spectrum.

low: int index to start at
low: int index to start at
high: int index to end at
"""
cs = self.cs[low:high]
Expand Down Expand Up @@ -400,7 +400,7 @@ def make_wave(self):
for t, spectrum in sorted(self.spec_map.iteritems()):
wave = spectrum.make_wave()
n = len(wave)

if self.window_func:
window = 1 / self.window_func(n)
wave.window(window)
Expand Down Expand Up @@ -453,7 +453,7 @@ def __or__(self, other):
"""Concatenates two waves.

other: Wave

returns: Wave
"""
if self.framerate != other.framerate:
Expand Down Expand Up @@ -567,8 +567,12 @@ def plot(self, **options):
"""Plots the wave.

"""
n = len(self.ys)
ts = numpy.linspace(0, self.duration, n)
dt = 1.0 / self.framerate
ts = numpy.arange(self.start, self.start + self.duration, dt)
# Due to erroneous division, the ts calculated above and below
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waves now carry the ts around with them, so I think this is resolved.

# have slightly different elements
#n = len(self.ys)
#ts = numpy.linspace(self.start, self.start + self.duration, n)
thinkplot.plot(ts, self.ys, **options)

def corr(self, other):
Expand All @@ -581,7 +585,7 @@ def corr(self, other):
mat = self.cov_mat(other)
corr = mat[0][1] / math.sqrt(mat[0][0] * mat[1][1])
return corr

def cov_mat(self, other):
"""Covariance matrix of two waves.

Expand Down Expand Up @@ -680,7 +684,7 @@ def quantize(ys, bound, dtype):
if max(ys) > 1 or min(ys) < -1:
print 'Warning: normalizing before quantizing.'
ys = normalize(ys)

zs = (ys * bound).astype(dtype)
return zs

Expand Down Expand Up @@ -749,7 +753,7 @@ def plot(self, framerate=11025):
duration = self.period * 3
wave = self.make_wave(duration, start=0, framerate=framerate)
wave.plot()

def make_wave(self, duration=1, start=0, framerate=11025):
"""Makes a Wave object.

Expand All @@ -760,7 +764,7 @@ def make_wave(self, duration=1, start=0, framerate=11025):
returns: Wave
"""
dt = 1.0 / framerate
ts = numpy.arange(start, duration, dt)
ts = numpy.arange(start, start + duration, dt)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

ys = self.evaluate(ts)
return Wave(ys, framerate=framerate, start=start)

Expand All @@ -781,7 +785,7 @@ def infer_framerate(ts):

class SumSignal(Signal):
"""Represents the sum of signals."""

def __init__(self, *args):
"""Initializes the sum.

Expand All @@ -806,15 +810,15 @@ def evaluate(self, ts):
"""Evaluates the signal at the given times.

ts: float array of times

returns: float wave array
"""
return sum(sig.evaluate(ts) for sig in self.signals)


class Sinusoid(Signal):
"""Represents a sinusoidal signal."""

def __init__(self, freq=440, amp=1.0, offset=0, func=numpy.sin):
"""Initializes a sinusoidal signal.

Expand All @@ -840,7 +844,7 @@ def evaluate(self, ts):
"""Evaluates the signal at the given times.

ts: float array of times

returns: float wave array
"""
phases = PI2 * self.freq * ts + self.offset
Expand All @@ -854,7 +858,7 @@ def CosSignal(freq=440, amp=1.0, offset=0):
freq: float frequency in Hz
amp: float amplitude, 1.0 is nominal max
offset: float phase offset in radians

returns: Sinusoid object
"""
return Sinusoid(freq, amp, offset, func=numpy.cos)
Expand All @@ -866,20 +870,20 @@ def SinSignal(freq=440, amp=1.0, offset=0):
freq: float frequency in Hz
amp: float amplitude, 1.0 is nominal max
offset: float phase offset in radians

returns: Sinusoid object
"""
return Sinusoid(freq, amp, offset, func=numpy.sin)


class SquareSignal(Sinusoid):
"""Represents a square 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
Expand All @@ -890,12 +894,12 @@ def evaluate(self, ts):

class SawtoothSignal(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
Expand All @@ -906,12 +910,12 @@ def evaluate(self, ts):

class ParabolicSignal(Sinusoid):
"""Represents a parabolic 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
Expand All @@ -923,12 +927,12 @@ def evaluate(self, ts):

class GlottalSignal(Sinusoid):
"""Represents a periodic signal that resembles a glottal 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
Expand All @@ -940,12 +944,12 @@ def evaluate(self, ts):

class TriangleSignal(Sinusoid):
"""Represents a triangle 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
Expand All @@ -957,7 +961,7 @@ def evaluate(self, ts):

class Chirp(Signal):
"""Represents a signal with variable frequency."""

def __init__(self, start=440, end=880, amp=1.0):
"""Initializes a linear chirp.

Expand All @@ -981,7 +985,7 @@ def evaluate(self, ts):
"""Evaluates the signal at the given times.

ts: float array of times

returns: float wave array
"""
freqs = numpy.linspace(self.start, self.end, len(ts)-1)
Expand All @@ -1005,12 +1009,12 @@ def _evaluate(self, ts, freqs):

class ExpoChirp(Chirp):
"""Represents a signal with varying frequency."""

def evaluate(self, ts):
"""Evaluates the signal at the given times.

ts: float array of times

returns: float wave array
"""
start, end = math.log10(self.start), math.log10(self.end)
Expand All @@ -1020,20 +1024,20 @@ def evaluate(self, ts):

class SilentSignal(Signal):
"""Represents silence."""

def evaluate(self, ts):
"""Evaluates the signal at the given times.

ts: float array of times

returns: float wave array
"""
return numpy.zeros(len(ts))


class _Noise(Signal):
"""Represents a noise signal (abstract parent class)."""

def __init__(self, amp=1.0):
"""Initializes a white noise signal.

Expand All @@ -1057,7 +1061,7 @@ def evaluate(self, ts):
"""Evaluates the signal at the given times.

ts: float array of times

returns: float wave array
"""
ys = numpy.random.uniform(-self.amp, self.amp, len(ts))
Expand All @@ -1071,7 +1075,7 @@ def evaluate(self, ts):
"""Evaluates the signal at the given times.

ts: float array of times

returns: float wave array
"""
ys = numpy.random.normal(0, 1, len(ts))
Expand All @@ -1089,7 +1093,7 @@ def evaluate(self, ts):
a uniform random series.

ts: float array of times

returns: float wave array
"""
#dys = numpy.random.normal(0, 1, len(ts))
Expand Down Expand Up @@ -1182,7 +1186,7 @@ def midi_to_freq(midi_num):
"""Converts MIDI note number to frequency.

midi_num: int MIDI note number

returns: float frequency in Hz
"""
x = (midi_num - 69) / 12.0
Expand Down Expand Up @@ -1240,7 +1244,7 @@ def main():
return

wfile = WavFileWriter()
for sig_cons in [SinSignal, TriangleSignal, SawtoothSignal,
for sig_cons in [SinSignal, TriangleSignal, SawtoothSignal,
GlottalSignal, ParabolicSignal, SquareSignal]:
print sig_cons
sig = sig_cons(440)
Expand Down
2 changes: 1 addition & 1 deletion trunk/book.tex
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ \section{Signals}
starts at $\cos \pi/2$, which is 0.
A sine signal with
{\tt offset=0} also starts at 0. In fact,
a cosine signal with {\tt offset=pi/2} is identical to a sine
a sine signal with {\tt offset=pi/2} is identical to a cosine
signal with {\tt offset=0}.

Signals have an \verb"__add__" method, so you can use the {\tt +}
Expand Down