The fundamental frequency of a note bowed on a stringed instrument (the Viola) may be extracted by selecting the lowest frequency peak from a series of overtones. An alternate way of extracting the fundamental frequency is also discussed for cases where the lowest frequency peak can not be read directly. The terms overtones and harmonics are used interchangeably in this post.
1.) Crop the raw time domain audio data to the region of interest. In this case the section where the note is being played.
2.) Take the FFT. Use fftshift in Matlab so the middle frequency is 0.
3.) Generate the frequency axis from the negative half max frequency to the positive half max frequency. (-fs/2 to fs/2). (fs is the sample frequency of the audio recorder)
4.) Window the frequency axis between 100 and 2000 Hz. This can be done with a mask in matlab.
5.) Find local maxima of the frequency domain signal (above a cutoff height, "MinProminence" using the islocalmax function).
6.) Select the lowest peak with respect to frequency. This is the Fundamental Frequency.
Matlab code is included in Appendices 1 and 2. In Appendix 1 the code for recording a segment of audio data in matlab is given. In Appendix 2 the code for extracting the fundamental frequency is given.
If the fundamental frequency is F, overtones will be located at frequencies 2F, 3F, 4F,etc... This is verified by selecting the lowest fundamental and multiplying by i = 1:length(xp) (where xp is the vector containing the frequency of the peaks.) The dotted red lines on the plots below show the expected location of the overtones. The fft plot also shows peaks at those locations.
$$ \begin{align*} F_3-F_2 &= 3F-2F\\ 3F-2F &= (3-2) F = F \end{align*} $$ The equation above shows that if the 2nd and 3rd harmonics are measured the fundamental frequency F may be extracted by taking the difference between them. This method is described in the discussion section below.b = xp(5:8); median(diff(b))
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %Set up figure %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% f1 = figure; f1.Position = [0 0 500 400]; %set window position a1 = axes(f1); %axes for plotting %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %Set up audiorecorder %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% fs = 44100; %sample frequency nBits = 16; nChannels = 1; ID = -1; %default audio input device recObj = audiorecorder(fs,nBits,nChannels,ID); recObj.TimerPeriod = .1; recObj.TimerFcn = @(s,e) update_plot(a1, recObj); %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %start/stop buttons %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% b1 = uicontrol(f1); b1.Style = 'pushbutton'; b1.String = 'start'; b1.BackgroundColor = 'green'; b1.Position = [500-30 400-60 30 30]; b1.Callback = @(s,e) record(recObj); %start recording b2 = uicontrol(f1); b2.Style = 'pushbutton'; b2.String = 'stop'; b2.BackgroundColor = 'red'; b2.Position = [500-30 400-90 30 30]; b2.Callback = @(s,e) stop(recObj); %stop recording %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %recObj Callback function %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% function update_plot(a1, recObj) y = getaudiodata(recObj); %get the audio data plot(a1,y); %plotaudio data end
load("OpenStrings.mat"); fs = 44100; %sample frequency (Hz) %crop A = A(160000:200000); D = D(138000:200000); G = G(124000:185000); C = C(145000:211000); %extract fundamental frequency Signal = A; %start with open string A sound( Signal, fs, 16 ); %play the sound F = abs(fftshift(fft(Signal))); x = linspace(-fs/2,fs/2, length(Signal)); %frequency domain x-labels mask = (x>100) & (x<2000); %min and max frequency (Hz) F = F(mask); x = x(mask)'; p = islocalmax(F,"MinProminence",3.5); %get peaks Fp = F(p); %amplitude xp = x(p); %frequency min(xp) cla plot(x,F); hold on; plot(xp,Fp,'*'); xlim([0 2000]); for i=1:length(xp)%plot overtones as a dotted line h = xp(1)*i; xline(h,'r:'); end