Your assignment is to write a program that will determine, given a sample of sound from a piano key, what key (1-88) and note (A-G#) it is.
The x axis is the number of seconds from the time the key was
struck; as
you can see, we are considering only 1/20 of a second of time.
This piano key happens to be the A above middle C, tuned at 440 Hertz
(abbreviated Hz). That means the string vibrates 440 times per second.
The y axis plots the amplitude of the compression of air around a
microphone
connected to my computer; you can think of it as a voltage level.
As you can see from the plot, the voltage level, or signal, spikes
once every 1/440 of a second.
Sound is a continous signal, so how do we reprent it inside the computer?
One way is to take a sample of the amplitude of
the signal once every, say, 1/8000 of a second. We can store
these samples in a large array. Whenever we want to look at the signal
at time t, where t is in seconds, we just multiply
t by 8000,
find the closest integer, and look in the array at that point. In fact,
this is exactly the way sound is stored on an ordinary CD, with samples
taken every 1/44,000 of a second.
How do you determine the frequency of a sampled sound? This situation
is complicated by the fact that in most situations, including the piano
key strike, there are many frequencies superimposed on each other. For
example, the A above also vibrates at 880 Hz, 1,320 Hz, and other frequencies,
although these show up at a lower amplitude. These harmonic
frequencies are what give the piano,
other instruments, your voice, and pretty much every other sound its
characteristic texture. However, we are now only interested in determining
the fundamental frequency.
The data are given to us in the time domain, i.e., we know, for any
given time, what the amplitude will be. It would be nicer if the data
were in the frequency domain, where we would know, for any given
frequency, what the amplitude will be. Then we can just look for the
frequency with the highest amplitude, and that's the fundamental frequency
(usually; it works for the middle part of the piano, at least).
The Fourier Transform of a function takes a function y(t)
in the time domain and ``transforms'' it into a function Y(f) in the
frequency domain. It works for any periodic continuous function.
The Fourier Transform, for your amusement, looks like this:
If you don't know what this means, don't worry; you don't have to.
typedef struct _complex { double real, /* real part */ imag; /* imaginary part */ } complex;Initially, you will just set the imaginary parts equal to 0, placing the values read in into the real parts.
cfft (complex_array, 16384)where complex_array is the name of your array. Your time-domain data will be replaced with the frequency domain data from the Fourier transform. Copy complex.o and complex.h to your directory. Call your program file piano.c. Use the following Makefile:
CC = gcc CFLAGS = -Wall LDLIBS = -lm all: piano piano: piano.c $(CC) -o piano piano.c complex.o $(LDLIBS)The complex.o object file also contains code for two functions:
complex make_complex (double, double); double magnitude_complex (complex);make_complex accepts two doubles and returns a complex with the first number as the real part, and the second as the imaginary part. magnitude_complex returns the magnitude of a complex number.
runner% ./piano 1.dat maximum amplitude is 4097.06 at 196.29 Hz, key = 35 key letter is G runner% ./piano 2.dat maximum amplitude is 6109.61 at 622.56 Hz, key = 55 key letter is D# runner% ./piano 3.dat maximum amplitude is 2902.94 at 154.79 Hz, key = 31 key letter is D#To turn in this program, do exactly this from the prompt on runner, after having compiled your program:
% cat piano.c > output % piano ~djimenez/cs1713/1.dat >> output % piano ~djimenez/cs1713/2.dat >> output % piano ~djimenez/cs1713/3.dat >> output % piano ~djimenez/cs1713/4.dat >> output % piano ~djimenez/cs1713/5.dat >> output % piano ~djimenez/cs1713/6.dat >> output % piano ~djimenez/cs1713/7.dat >> output % piano ~djimenez/cs1713/8.dat >> output % piano ~djimenez/cs1713/9.dat >> output % piano ~djimenez/cs1713/10.dat >> output % piano ~djimenez/cs1713/11.dat >> output % piano ~djimenez/cs1713/12.dat >> output % piano ~djimenez/cs1713/13.dat >> output % piano ~djimenez/cs1713/14.dat >> output % piano ~djimenez/cs1713/15.dat >> output % piano ~djimenez/cs1713/16.dat >> output % piano ~djimenez/cs1713/17.dat >> output % piano ~djimenez/cs1713/18.dat >> output % piano ~djimenez/cs1713/19.dat >> output % piano ~djimenez/cs1713/20.dat >> output % Mail -s "piano program" djimenez@ringer.cs.utsa.edu < outputThis will run your program on all the files and send me the output. (If you have written other files beside piano.c, e-mail me them, too, separately.) Expect the run time of this program to be rather long, on the order of a few seconds; an FFT is a computationally expensive operation, especially when there are 30 other students on runner running the same program.