Lab 5
Matlab/Python: Rudimentary Pitch Tracker
For your final Matlab assignment, we’re going to build a very basic pitch-tracker / resynthesizer. Our goal is to be able to take a monophonic (one note at a time) input file and generate a digital version that copies the original.
We will do this all in a single function, which will process the input signal in chunks, or blocks, and write to output a sawtoothTone
that follows the loudest frequency in each block:
[output] = trackPitch(input, fs, windowSize)
input
is the original source as a vector
fs
is the original sample rate
windowSize
is the size of the blocks we’ll break the input
into to analyze and resynthesize, in samples. This is sometimes also called ‘blocksize’ or ‘hopsize.’ The value you choose affects the resolution of the spectrum you can generate - the larger, the more accurately you can track the correct frequency. However, it also determines the temporal resolution of the resynthesized output - the pitch of the output will only change each windowSize
samples.
These are the steps you will need to follow to build your trackPitch()
function:
- Create a for loop that repeats
floor(length(input)/windowSize)
times (we won’t worry about any extra samples we might be missing at the end). - For each iteration of the for loop, we need to get a block of the input signal. For example, for the first repetition we want
block = input(1:windowSize)
in Matlab orblock = input[0:windowSize]
in Python, for the second iteration we wantblock = input(windowSize+1:2 * windowSize)
in Matlab orblock = input[windowSize:2 * windowSize]
in Python, and so forth. How can we use our for loop to get the right block based on what number iteration we are on? - Next, we use our
getSpectrum()
function from lab 3 on that block. We want to get the loudest frequency in that block, so use[M,I] = max(Y)
, whereY
is theY
returned fromgetSpectrum()
. TheI
value will be the index of the biggest value inY
, so we are looking for the corresponding value in theF
fromgetSpectrum()
, which will beF(I)
. -
Generate one block of your output sawtooth signal for the current iteration of your for loop. There are two ways you can do this:
- The easier (albeit slower and less correct) way to do this is to append a sawtooth at the frequency we found to
output
using the[A,B]
notation in Matlab or theA.append(B)
notation in Python, which will concatenate vectorsA
andB
. You’ll need to create an empty output vector before the for loop withoutput = []
. Then we can recursively append a sawtooth to our output on each iteration of the for loop by typingoutput = [output, sawtoothTone(fs, F(I), 0, windowSize/fs, 8)];
oroutput.append(sawtoothTone(fs, F(I), 0, windowSize/fs, 8))
(Here I’ve used 8 harmonics in my sawtooth tone, but feel free to use however many you want). - The cleaner and faster way to do this is to create the correct length
output
vector before your for loop (hint: usezeros()
ornp.zeros()
), and then use the same indexing within your for loop that you use to access samples in theinput
to assign thesawtoothTone()
you generate for that block to youroutput
.
- The easier (albeit slower and less correct) way to do this is to append a sawtooth at the frequency we found to
- Either way, you should end up with a continuous sawtooth wave output that follows the pitch of your original
input
.
Once you’re done with your trackPitch()
function, make a short recording (10-15 seconds) of yourself singing or playing a monophonic instrument (using Garageband, Audacity, or whatever you wish, reach out to Camille if you need help doing this) and use trackPitch()
to generate a digital sawtooth version. Experiment with different power-of-2 values for windowSize
to see what gives you the best balance of frequency and time resolution.
Matlab/Python Deliverables:
Submit your trackPitch()
function along with your original recorded .wav file and generated digital sawtooth version. Report the value you used for windowSize
for your final output in a comment in your function.
See Canvas for due dates.