-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathsigproc-outline.py
310 lines (235 loc) · 8.89 KB
/
sigproc-outline.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# PyCon 2013 -- Mel Chua's Signal Processing workshop
# let's import the libraries we need to do fourier transforms
from numpy import *
# let's create some data -- here we're making a signal
# that is 2 added tones (1250Hz and 625Hz, at a 10kHz sample rate)
x = arange(256.0)
sig = sin(2*pi*(1250.0/10000.0)*x) + sin(2*pi*(625.0/10000.0)*x)
# what does this look like?
# let's import the plotting libraries
import matplotlib.pyplot as pyplot
# and then plot the signal...
pyplot.plot(sig)
pyplot.savefig('sig.png')
# and now we're done, so let's clear the plot.
pyplot.clf()
# while we're at it, let's make a graphing function
def makegraph(data, filename):
pyplot.clf()
pyplot.plot(data)
pyplot.savefig(filename)
# let's take the FFT of the signal
# notice it's a real-valued input, so we do RFFT
data = fft.rfft(sig)
makegraph(data, 'fft0.png')
# note that we had 256 datapoints in sig (because x = arange(256.0))
# but we're only graphing 127. That's because in real-valued
# functions, the output is symmetric -- rfft only computes one,
# not the duplicates.
# we're interested in the magnitude, so...
data = abs(data)
makegraph(data, 'fft1.png')
# whoa, what happened? we went from a jaggedy line to two peaks...
# ...and the value of the peaks is way higher? that first peak
# went from close to -3 to close to 130! what gives?
# also, why are the peaks suddenly the same size?
# well, the fft gives us a complex number.
type(fft.rfft(sig)[0])
# and abs takes the magnitude of that complex number.
# it returns real numbers.
type(abs(fft.rfft(sig)[0]))
# if we take a look, we can see it's the 16th and 32nd values
# that are the peaks in both graphs...
fft.rfft(sig)
abs(fft.rfft(sig))
# so let's look at them both here.
# this is an imaginary number, and only the real part is plotted
# but it's got a large imaginary part
fft.rfft(sig)[16]
# so when we take the magnitude with abs, that imaginary part adds in.
abs(fft.rfft(sig)[16])
# and when we look at the 32nd value...
fft.rfft(sig)[32]
abs(fft.rfft(sig)[32])
# now, back to our graph. we see two peaks. Good!
# So, we're seeing the two peaks... but this is raw power output
# Let's normalize to decibels -- that's how we usually think of volume
# http://en.wikipedia.org/wiki/Decibel
# decibels are 10*log10, so...
data = 10*log10(data)
makegraph(data, 'fft2.png')
# hey look, quantization noise! We'll talk about this in a little bit...
# recall that we added 2 sinusoids at 625 and 1250 Hz...
# do those peaks correspond to those sinusoids on the graph?
# well, the numpy fft function goes from 0-5000Hz
# so the X-axis is marked in increments that correspond to
# values of 0-5000Hz divided into 128 slices
# 5000/128 = 39.0625 Hz per tick mark
# the two peaks here are:
# (5000/128)*16 = 625 Hz
# (5000/128)*32 = 1250 Hz
# which are the tones we used, so we win!
# this smushes all the times together, what if we want to see them again?
# here's another visualization called a spectrogram...
# STOP: SLIDE
from pylab import specgram
pyplot.clf()
sgram = specgram(sig)
pyplot.savefig('sgram.png')
# do you see how the spectrogram becomes the spectrum when sliced?
# now let's do this with a more complex sound.
# https://www.pythonanywhere.com/user/mchua/files/home/mchua/flute.wav
# we need libraries for working with wave files
# http://docs.scipy.org/doc/scipy/reference/io.html
import scipy
# for people working through this line by line
# you'll also need to...
from scipy.io.wavfile import read
# Random Coolness Note:
# Python does have a built in 'wave' library
# so why are we using scipy?
# See wavlibraryexample.py for the eqiuvalent implementation
# using the built-in wave library.
# how to import it...
def getwavdata(file):
return scipy.io.wavfile.read(file)[1]
# let's take a closer look at what's going on
# data = scipy.io.wavfile.read('flute.wav') returns a tuple
# the first number is the sampling rate (samples/second)
# notice it's 44100, which is CD-quality samping
# the second is a numpy array with the data (dtype int16)
# (bonus: why int16? see http://docs.scipy.org/doc/numpy/user/basics.types.html)
# and think about space-saving.
# So let's get the data...
audio = getwavdata('flute.wav')
# hang on!
# how do we make sure we've got the same data out as in?
# well, we could write it back to a wav file and play it back.
# how do we do that?
def makewav(data, outfile, samplerate):
scipy.io.wavfile.write(outfile, samplerate, data)
# note: this assumes the data is in a numpy array
# if not, convert before passing to makewav
# example: array(data, dtype=int16)
# let's listen
makewav(audio, 'foo.wav', 44100)
# let's see what this looks like in the time domain
makegraph(audio[0:1024], 'flute.png')
# These do sort of the same thing, btw
# I don't really care about the difference
numpy.fft.rfft(audio)
scipy.fftpack.rfft(audio)
# What does the fft look like?
audiofft = fft.rfft(audio)
makegraph(audiofft, 'flute_fft.png')
# oh wait, we wanted to normalize this
audiofft = abs(audiofft)
audiofft = 10*log10(audiofft)
makegraph(audiofft, 'flute_fft.png')
# and the spectrogram?
pyplot.clf()
sgram = specgram(audio)
pyplot.savefig('flutespectro.png')
# what note is the flute playing?
# http://www.bgfl.org/custom/resources_ftp/client_ftp/ks2/music/piano/flute.htm
# sounds like a B
# which is about 494Hz
# http://en.wikipedia.org/wiki/Piano_key_frequencies
# MID-WORKSHOP BREAK
# NOTES PEOPLE ARE FINDING:
# sin generates really, really tiny amplitudes
# (compare to the magnitude of the data you get when
# you read in the flute wavfile)
# how can you increase the amplitude of your signal?
# also: once you get an audible .wav file, does it sound like
# a clean tone? (What do you think might be the cause?)
def makesinwav(freq, amplitude, sampling_freq, num_samples):
return array(sin(2*pi*freq/float(sampling_freq)*arange(float(num_samples)))*amplitude,dtype=int16)
# if you're having trouble getting your sine wave to sound like a pure tone...
def savewav(data, outfile, samplerate):
#coerce to dtype int16 from float value
out_data = array(data, dtype=int16)
scipy.io.wavfile.write(outfile, samplerate, out_data)
# sample rate illustrations
audio = getwavdata('flute.wav')
makewav(audio, 'fluteagain44100.wav', 44100)
makewav(audio, 'fluteagain22000.wav', 22000)
makewav(audio, 'fluteagain88200.wav', 88200)
# Refresher: the fft takes us from the time
# to the frequency domain
flutefft = fft.rfft(audio)
# Going... backwards!
# Using the inverse fast fourier transform to go
# from the frequency to the time domain
reflute= fft.irfft(flutefft, len(audio))
reflute_coerced = array(reflute, dtype=int16) # coerce it
makewav(reflute_coerced, 'fluteregenerated.wav', 44100)
# We can now look at the graph of our flute sound
# in the frequency domain...
makegraph(10*log10(abs(flutefft)), 'flutefftdb.png')
# ...decide to low-pass filter it...
flutefft[5000:] = 0
# and see what the frequency spectrum looks like now.
makegraph(10*log10(abs(flutefft)), 'flutefft_lowpassed.png')
# go back to the time domain so we can listen
# to the low-passed flute sound
reflute= fft.irfft(flutefft, len(audio))
reflute_coerced = array(reflute, dtype=int16) # coerce it
makewav(reflute_coerced, 'flute_lowpassed.wav', 44100)
# TODO: VOCODER CODE - "AVE MARIA"
---
# filtering noisy things
import random
x = arange(256.0)
sig = sin(2*pi*(1250.0/10000.0)*x) + sin(2*pi*(625.0/10000.0)*x)
noisy = [x/2.0 + random.random()*0.1 for x in sig]
pyplot.clf()
sgram = specgram(sig)
pyplot.savefig('nonoise.png')
pyplot.clf()
sgram = specgram(noisy)
pyplot.savefig('withnoise.png')
# looks awful!
# okay, let's go back to something simpler
def lowpassfilter(signal):
for i in range(20, len(signal)-20):
signal[i] = 0
######### END OF TUTORIAL ##########
######### BEGIN UNUSED CODE SNIPPETS ########
# (created just in case we needed them)
## Appendix 1: A more computationally efficient way to
## generate a sine wave
# a more efficient way to make a signal
duration = 4 # seconds
samplerate = 44100 # Hz
samples = duration*samplerate
frequency = 440 # Hz
# just compute one period
period = samplerate / float(frequency) # in sample points
omega = 2*pi / period # 2*pi*frequency / samplerate
xaxis = N.arange(int(period),dtype = N.float) * omega
ydata = 16384 * N.sin(xaxis)
# and then repeat it
signal = N.resize(ydata, (samples,))
## Appendix 2: Hanning windows
# http://en.wikipedia.org/wiki/Window_function#Hann_.28Hanning.29_window
from scipy.signal import hann
audio = getwavdata('flute.wav')
window = hann(len(audio))
audio = audio * window
## Appendix 3: Labeling graphs
# If you want to have labels for your graphs...
# the graph
pyplot.plot(audio)
# label the axes
pyplot.ylabel("Amplitude")
pyplot.xlabel("Time (samples)")
# set the title
pyplot.title("Flute Sample")
## Appendix 4: useful terms to look up
# We didn't have time to discuss these, but if you
# do further work in signal processing, you should
# know these vocabulary words
# impulse response
# transfer function
# envelope