FF0605
FF0605

Reputation: 441

How to find the frequency of a periodic sound signal?

I'm working on sound signals of a walking pattern, which has obvious regular patterns:

Sound of a walking person, X in seconds, Y in sound signal

Then I thought I can get the frequency of walking (approximately 1.7Hz from the image) using FFT function:

    x = walk_5; % Walking sound with a size of 711680x2 double
    Fs = 48000; % sound frquency
    L=length(x); 

    t=(1:L)/Fs; %time base
    plot(t,x);
    figure;

    NFFT=2^nextpow2(L);      
    X=fft(x,NFFT);       
    Px=X.*conj(X)/(NFFT*L); %Power of each freq components       
    fVals=Fs*(0:NFFT/2-1)/NFFT;      
    plot(fVals,Px(1:NFFT/2),'b','LineSmoothing','on','LineWidth',1);         
    title('One Sided Power Spectral Density');       
    xlabel('Frequency (Hz)')         
    ylabel('PSD');

But then it doesn't give me what I expected:

FFT result:

FFT result

zoom image has lots of noises: zoom image has lots of noises

and there is no information near 1.7Hz enter image description here

Here is the graph from log domain using

    semilogy(fVals,Px(1:NFFT));

It's pretty symmetric though: enter image description here

I couldn't find anything wrong with my code. Do you have any solutions to easily extract the 1.7Hz from the walking pattern?

here is the link for the audio file in mat https://www.dropbox.com/s/craof8qkz9n5dr1/walk_sound.mat?dl=0

Thank you very much!

Kai

Upvotes: 1

Views: 2827

Answers (2)

jojeck
jojeck

Reputation: 987

I suggest you to forget about DFT approach since your signal is not appropriate for this type of analysis due to many reasons. Even by looking on the spectrum in range of frequencies that you are interested in, there is no easy way to estimate the peak:

enter image description here

Of course you could try with PSD/STFT and other funky methods, but this is an overkill. I can think of two, rather simple methods, for this task.


First one is based simply on the Auto Correlation Function.

  1. Calculate the ACF
  2. Define the minimum distance between them. Since you know that expected frequency is around 1.7Hz, then it corresponds to 0.58s. Let's make it 0.5s as the minimum distance.
  3. Calculate the average distance between peaks found.

This gave me an approximate frequency of 1.72 Hz .

enter image description here


Second approach is based on the observation to your signal already has some peaks which are periodic. Therefore we can simply search for them using findpeaks function.

  1. Define the minimum peak distance in a same way as before.
  2. Define the minimum peak height. For example 10% of maximum peak.
  3. Get the average difference.

This gave me an average frequency of 1.7 Hz.

enter image description here

Easy and fast method. There are obviously some things that can be improved, such as:

  • Refining thresholds
  • Finding both positive and negative peaks
  • Taking care of some missing peaks, i.e. due to low amplitude

Anyway that should get you started, instead of being stuck with crappy FFT and lazy semilogx.


Code snippet:

load walk_sound

fs = 48000;
dt = 1/fs;

x = walk_5(:,1);
x = x - mean(x);
N = length(x);
t = 0:dt:(N-1)*dt;

% FFT based
win = hamming(N);
X = abs(fft(x.*win));
X = 2*X(1:N/2+1)/sum(win);
X = 20*log10(X/max(abs(X)));
f = 0:fs/N:fs/2;

subplot(2,1,1)
plot(t, x)
grid on
xlabel('t [s]')
ylabel('A')
title('Time domain signal')

subplot(2,1,2)
plot(f, X)
grid on
xlabel('f [Hz]')
ylabel('A [dB]')
title('Signal Spectrum')

% Autocorrelation
[ac, lag] = xcorr(x);
min_dist = ceil(0.5*fs);
[pks, loc] = findpeaks(ac, 'MinPeakDistance', min_dist);

% Average distance/frequency
avg_dt = mean(gradient(loc))*dt;
avg_f = 1/avg_dt;

figure
plot(lag*dt, ac);
hold on
grid on
plot(lag(loc)*dt, pks, 'xr')
title(sprintf('ACF - Average frequency: %.2f Hz', avg_f))


% Simple peak finding in time domain
[pkst, loct] = findpeaks(x, 'MinPeakDistance', min_dist, ...
                            'MinPeakHeight', 0.1*max(x));

avg_dt2 = mean(gradient(loct))*dt;
avg_f2 = 1/avg_dt2;

figure
plot(t, x)
grid on
hold on
plot(loct*dt, pkst, 'xr')
xlabel('t [s]')
ylabel('A')
title(sprintf('Peak search in time domain - Average frequency: %.2f Hz', avg_f2))

Upvotes: 3

Adjwilley
Adjwilley

Reputation: 217

Here's a nifty solution:

Take the absolute value of your raw data before taking the FFT. The data has a ton of high frequency noise that is drowning out whatever low frequency periodicity is present in the signal. The amplitude of the high frequency noise gets bigger every 1.7 seconds, and the increase in amplitude is visible to the eye, and periodic, but when you multiply the signal by a low frequency sine wave and sum everything you still end up with something close to zero. Taking the absolute value changes this, making those amplitude modulations periodic at low frequencies.

Try the following code comparing the FFT of the regular data with the FFT of abs(data). Note that I took a few liberties with your code, such as combining what I assume were the two stereo channels into a single mono channel.

x = (walk_5(:,1)+walk_5(:,2))/2; % Convert from sterio to mono
Fs = 48000; % sampling frquency
L=length(x); % length of sample
fVals=(0:L-1)*(Fs/L); % frequency range for FFT

walk5abs=abs(x); % Take the absolute value of the raw data

Xold=abs(fft(x)); % FFT of the data (abs in Matlab takes complex magnitude)
Xnew=abs(fft(walk5abs-mean(walk5abs))); % FFT of the absolute value of the data, with average value subtracted

figure;
plot(fVals,Xold/max(Xold),'r',fVals,Xnew/max(Xnew),'b')
axis([0 10 0 1])
legend('old method','new method')

[~,maxInd]=max(Xnew); % Index of maximum value of FFT
walkingFrequency=fVals(maxInd) % print max value

And plotting the FFT for both the old method and the new, from 0 to 10 Hz gives:

FFT of walk_5 data before and after taking absolute value

As you can see it detects a peak at about 1.686 Hz, and for this data, that's the highest peak in the FFT spectrum.

Upvotes: 1

Related Questions