Pitch Detection by Harmonics
Matlab. This is my pitch detection algorithm. It takes mono wav. file as an input, and checks its fundamental and next three harmonics. Firstly, a threshold value is determined after finding the maximum peak by FFT. Based on magnitude spectrum in log scale, it chooses %70 of max peak as a threshold value to filter. It can be changed if needed. The algorithm replaces the amplitude values with zeros under the threshold. Then it determines the bandwidth value for the bands. After finding max peaks for each band, it converts those values from frequency bin to frequency Hz. Finally, it checks consistency for harmonics with the fundamental frequency in the range of %5 (higher or lower) and takes the mean value in the frequency list. If the harmonic is away from the fundamental, the algorithm excludes it.
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 |
function final_freq = pitch_detection tic [y, Fs] = audioread('strings.wav'); %input audio file (mono) t = 0.2; %starting time (secs) ystart = y(floor(1+t*Fs):length(y)); %segment of the input for FFT N = length(ystart); %FFT size for the segment itself Y = fft(ystart); %taking FFT of the signal X = Y'; %laying fft data onto x-axis mX = abs(X); %symmetric magnitude mX = mX(1:N/2); %taking the first half mX = 20*log10(mX); %magnitude in decibels %Calculating Stage - bands for fundamental and next 3 harmonics threshold = 0.7 * max(mX); %threshold to filter mX fmX = mX; fmX(fmX < threshold) = 0; %fmX = filtered magnitude array for i = 1:length(fmX) %finding the bandwidth if fmX(i) ~= 0 %finding the first bin that reaches any value bandwidth = i - 1; if bandwidth < 2 %if it gets a value at the beginning bandwidth = 2; end break end end base = zeros(1, floor(N/2)); %base to check frequency for each band flist = []; %temporary frequency list for j = 1:4 start = floor((2*j-1)*bandwidth/2); finish = floor((2*j+1)*bandwidth/2); base(start:finish) = fmX(start:finish); %creating band to detect [~, freq_bin] = max(base); %detecting frequency bin in each band freq = (Fs*freq_bin)/(j*N); %changing frequency bin to frequency flist = [flist, freq]; %creating temporary frequency list base = zeros(1, floor(N/2)); %preparing base for next band end %Consistency Checking Stage - of harmonics with the fundamental frequency %Harmonics were normalised by their ranks; 2, 3, and 4 %Comparing absolute value of difference with %5 range to create final list frequencies = flist(1); %funtamental frequency for k = 2:4 if abs(flist(1) - flist(k)) <= 0.05 * flist(1) frequencies = [frequencies, flist(k)]; end end %Plotting Stage - filtered spectrum and bands for detecting peaks subplot(2,1,1); plot(mX); title('Magnitude Spectrum'); xlabel('frequency bins'); ylabel('dB'); subplot(2,1,2); plot(fmX); for k = 1:j+1 xaxis = (2*k-1)/2 * bandwidth; line([xaxis xaxis], [0 100], 'Color','red','LineStyle','--'); end title('Filtered Spectrum and 4 Bands to Detect the Highest Peaks'); xlabel('frequency bins'); ylabel('dB'); %Final Stage - taking average value of the frequency list format long g final_freq = round(mean(frequencies), 2); if final_freq < 27.5 || final_freq > 13290 error('Not detectable') end toc end |
It is basically designed for single note pitches. When the sound is not clear, complex, or does not have any clear body in the musical frequency range, it gives not detectable error. If an orchestral segment is used and some notes are dominant there (particularly low range), it might give very low frequency as a result.
I used many mono files to see how it works. It works very well in general. As an example, I would like to use a strings fragment here. It finds frequencies in order (the first one is fundamental): approximately 1161.09, 1164.62, 1161.17, 1178. The final value is 1166.22 Hz. This violin ensemble played D6 in this segment.
References:
[1] Pitch Frequency List, The University of Virginia