How to find the frequency of a periodic sound signal? - matlab

I'm working on sound signals of a walking pattern, which has obvious regular patterns:
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:
zoom image has lots of noises:
and there is no information near 1.7Hz
Here is the graph from log domain using
semilogy(fVals,Px(1:NFFT));
It's pretty symmetric though:
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

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:
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.
Calculate the ACF
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.
Calculate the average distance between peaks found.
This gave me an approximate frequency of 1.72 Hz .
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.
Define the minimum peak distance in a same way as before.
Define the minimum peak height. For example 10% of maximum peak.
Get the average difference.
This gave me an average frequency of 1.7 Hz.
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))

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:
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.

Related

High-frequency spur after performing an FFT in MATLAB

I have a modulated signal, and now I want to perform an FFT. However, I am getting a spur at a high frequency, which should not be there (and if it should, I have no clue as to why).
Lvl=[0.5,0.9,0.5,0.5,0.1,0.1,0.9,0.5];
fa=60; %the frequency of the parasitic source in hertz
np=2; %number of periods per bit
kl=length(Lvl);
t=0:0.01*np/fa:np*kl/fa;
Sig=sin(2*pi*fa*t);
for n=1:1:101
Sig(n)=Sig(n)*Lvl(1);
end
for n=102:1:201
Sig(n)=Sig(n)*Lvl(2);
end
for n=202:1:301
Sig(n)=Sig(n)*Lvl(3);
end
for n=302:1:401
Sig(n)=Sig(n)*Lvl(4);
end
for n=402:1:501
Sig(n)=Sig(n)*Lvl(5);
end
for n=502:1:601
Sig(n)=Sig(n)*Lvl(6);
end
for n=602:1:701
Sig(n)=Sig(n)*Lvl(7);
end
for n=702:1:801
Sig(n)=Sig(n)*Lvl(8);
end
plot(t,Sig)
%FFT
y = fft(Sig);
f = (0:length(y)-1)*(1/(0.01*np/fa))/length(y);
plot(f,abs(y))
title('Magnitude')
I'm expecting just a spike at 60Hz with spurs around it, but instead I'm getting that and a large spike at almost 3kHz with spurs around it.
This peak at almost 3 kHz should be there, since the fft of a real is signal symmetric around the nyquist frequency (actually complex conjugate). The nyquist frequency is half the samping frequency, in your case sampling is done at 3000 Hz, thus the nyquist frequency is 1500 Hz. If you look closer at the peak, you will see that it is at 2940 Hz (which is 3000-60 Hz), due to the fact that the fft is mirrored around 1500 Hz.
There are plenty of sources that explain why this is a property of the Fourier transform (e.g. here).
The actual Fourier transform would be mirrored around the zero frequency, but the fft gives you the fast Fourier transform, which is mirrored around the nyquist frequency. You can use fftshift to center the spectrum around the zero frequency.
I took the liberty to shorten your code, by avoiding repetition of several for-loops, and added the fftshift. Since your signal is real, you can also choose to show only one side of the fft, but I'll leave that up to you.
Lvl=[0.5,0.9,0.5,0.5,0.1,0.1,0.9,0.5];
fa=60; % the frequency of the parasitic source in hertz
np=2; % number of periods per bit
kl = length(Lvl);
dt = 0.01*np/fa; % time step
Tend = np*kl/fa - dt; % time span
t = 0:dt:Tend; % time vector
N = length(t); % number samples
Sig=sin(2*pi*fa*t);
for n = 1:kl
ids = (1:100) + (n-1)*100;
Sig(ids) = Sig(ids).*Lvl(n);
end
% FFT
Y = fft(Sig);
fv = (0:N-1)/(N*dt); % frequency range
% FFT shift:
Y_shift = fftshift(Y);
fv_shift = (-N/2:N/2-1)/(N*dt); % zero centered frequency vector
% Plot
figure(1); clf
subplot(311)
plot(t,Sig)
title('Signal')
subplot(312)
plot(fv,abs(Y))
title('FFT Magnitude')
subplot(313)
plot(fv_shift,abs(Y_shift))
title('FFT Magnitude zero shift')

Matlab FFT - Scaling y-axis

As a data basis, I have measured data the volts were recorded.
Matlab will now be used to perform an FFT.
I have the following questions:
- What unit do I have on the ordinate axis after the FFT? Also volts?
- How is scaled correctly? By hiding the negative frequencies (Nyquist), I would actually have to double the amplitude, right?
- Do I have to multiply all values of the FFT again with 20 * log10 (FFT) to represent the ordinate in db?
Thank you so much for your support!
Frank
Matlab example:
load('TimeDomain.mat')%loading of the time domain signal
L=2500; %length of the signal
Fs=500000;%sampling frequency
N=2^nextpow2(L);%scale factor
t=(0:L-1)*10^-3;%time domain array
f=linspace(0,Fs/2,length(t));%frequency domain array
FFT=abs(fft(Timedomain,N));
figure(1)
plot(f,FFT(1:2500))
Yes, after the FFT, the unit of the ordinate axis will still be volts.
You can scale it by dividing by the number of samples of your signal, then you can indeed multiplying by two (except the first and last elements that represents respectively the frequency 0 and Fs/2) if you want to plot all the spectrum in the positive side.
Then if you want to plot in dB, you can use the function mag2db which applies the formula that you said.
I found some weirdnesses in your code, so I suggest some fixes. My Timedomain signal is a 100 kHz sine at 1 V.
Fs = 500000; % sampling frequency
L = 2500; % length of the signal
t = (0:L-1)/Fs; % time domain array
f = linspace(0, Fs/2, L/2+1); % frequency domain array
Timedomain = cos(2*pi*100000*t); % Input signal
FFT = abs(fft(Timedomain)/L);
FFT(2:L/2) = 2*FFT(2:L/2);
%% Plots
subplot(2,1,1);
plot(f, FFT(1:L/2+1)); xlabel('Frequency (Hz)'); ylabel('Tension (V)');
subplot(2,1,2);
plot(f, mag2db(FFT(1:L/2+1))); xlabel('Frequency (Hz)'); ylabel('Tension (dBV)');
This returns:

How do I create band-limited (100-640 Hz) white Gaussian noise?

I would like to create 500 ms of band-limited (100-640 Hz) white Gaussian noise with a (relatively) flat frequency spectrum. The noise should be normally distributed with mean = ~0 and 99.7% of values between ± 2 (i.e. standard deviation = 2/3). My sample rate is 1280 Hz; thus, a new amplitude is generated for each frame.
duration = 500e-3;
rate = 1280;
amplitude = 2;
npoints = duration * rate;
noise = (amplitude/3)* randn( 1, npoints );
% Gaus distributed white noise; mean = ~0; 99.7% of amplitudes between ± 2.
time = (0:npoints-1) / rate
Could somebody please show me how to filter the signal for the desired result (i.e. 100-640 Hz)? In addition, I was hoping somebody could also show me how to generate a graph to illustrate that the frequency spectrum is indeed flat.
I intend on importing the waveform to Signal (CED) to output as a form of transcranial electrical stimulation.
The following is Matlab implementation of the method alluded to by "Some Guy" in a comment to your question.
% In frequency domain, white noise has constant amplitude but uniformly
% distributed random phase. We generate this here. Only half of the
% samples are generated here, the rest are computed later using the complex
% conjugate symmetry property of the FFT (of real signals).
X = [1; exp(i*2*pi*rand(npoints/2-1,1)); 1]; % X(1) and X(NFFT/2) must be real
% Identify the locations of frequency bins. These will be used to zero out
% the elements of X that are not in the desired band
freqbins = (0:npoints/2)'/npoints*rate;
% Zero out the frequency components outside the desired band
X(find((freqbins < 100) | (freqbins > 640))) = 0;
% Use the complex conjugate symmetry property of the FFT (for real signals) to
% generate the other half of the frequency-domain signal
X = [X; conj(flipud(X(2:end-1)))];
% IFFT to convert to time-domain
noise = real(ifft(X));
% Normalize such that 99.7% of the times signal lies between ±2
noise = 2*noise/prctile(noise, 99.7);
Statistical analysis of around a million samples generated using this method results in the following spectrum and distribution:
Firstly, the spectrum (using Welch method) is, as expected, flat in the band of interest:
Also, the distribution, estimated using histogram of the signal, matches the Gaussian PDF quite well.

frequency domain interpolation changes the signal spectrum

I am working on some experimental data related to a sine-sweep excitation.
I first reconstructed the signal using the amplitude and frequency information I get from the data file:
% finz: frequency
% ginz: amplitude
R = 4; % sweep rate
tz = 60/R*log2(finz/finz(1)); % time
u_swt = sin(2*pi*((60*finz(1)/(R*log(2.))*(2.^(R/60*tz)-1))));
time_sign = ginz.*u_swt;
freq_sign = fft(time_sign);
This is what I obtain:
I then tried to interpolate the frequency data before computing the time signal to obtain a 'nicer' signal (having more samples it should be easier to reconstruct it):
ginz = interp(ginz,200);
finz = interp(finz,200);
But now the spectrum is changed:
Why the frequency spectrum is so different? Am I doing something wrong in the interpolation? Should I not interpolate the data?
The details of the signal you are working with are not clear to me. For instance, can you please provide typical examples of finz and ginz? Also, it is not clear what you are hoping to achieve through interpolation, so it is hard to advise on its use.
However, if you interpolate a time series you should expect its spectrum to change as it increases the sampling frequency. The frequency of an interpolated signal will become smaller relative to the new sampling frequency. Therefore, the signal spectrum will be (not being very technical here) pushed towards zero. I have provided a script below which creates white Gaussian noise, and plots the spectrum for different levels of interpolation. In the first subfigure with no interpolation, the spectrum is uniformly occupied (by design - white noise). In subsequent subfigures, with increasing interpolation the occupied spectrum becomes smaller and smaller. Hope this helps.
% white Gaussian noise (WGN)
WGN = randn(1,1000);
% DFT of WGN
DFT_WGN = abs(fft(WGN));
% one-sided spectrum
DFT_WGN = DFT_WGN(1:length(WGN)/2);
% interpolated WGN by factor of 2 (q = 2)
WGN_interp_2 = interp(WGN,2);
% DFT of interpolated WGN
DFT_WGN_interp_2 = abs(fft(WGN_interp_2));
% one-sided spectrum
DFT_WGN_interp_2 = DFT_WGN_interp_2(1:length(DFT_WGN_interp_2 )/2);
% interpolated WGN by factor of 10 (q = 10)
WGN_interp_10 = interp(WGN,10);
% DFT of interpolated WGN
DFT_WGN_interp_10 = abs(fft(WGN_interp_10));
% one-sided spectrum
DFT_WGN_interp_10 = DFT_WGN_interp_10(1:length(DFT_WGN_interp_10 )/2);
figure
subplot(3,1,1)
plot(DFT_WGN)
ylabel('DFT')
subplot(3,1,2)
plot(DFT_WGN_interp_2)
ylabel('DFT (q:2)')
subplot(3,1,3)
plot(DFT_WGN_interp_10)
ylabel('DFT (q:10)')

How do I obtain Energy spectrum of a signal after FFT in Matlab?

EDIT:
I stumbled on this explanation to obtain the energy spectrum from an IEEE paper(Open Circuit Fault Diagnosis in 3 phase uncontrolled rectifiers, Rahiminejad, Diduch, Stevenson, Chang).
"A recorded sample of the signal containing a number of samples equivalent to 4T is captured and its FFT is determined using an FFT size equal to the record length (where T is the fundamental period).
Assuming the FFT size is matched to 4 periods of a periodic waveform, every 4th FFT bin will coincide with a harmonic frequency, in particular the center of FFT bin 4k+1 will coincide with the kth harmonic frequency.
The energy of the kth harmonic is calculated as the sum of the squared magnitudes of the 5 consecutive FFT values centred at bin 4k+1. The additional FFT values are included in the harmonic energy calculation so as to reduce the sensitivity of the calculated energy to an error in the frequency estimate which oculd result in the kth harmonic peak shifting away from bin 4k+1."
I do not fully understand the passage above. In my limited understanding, the bold line
refers to the sum of the squared magnitudes of the output of function fft(), i.e. the complex fourier series coefficients.
Can someone please show some light into obtaining the energy spectrum ?
#fpe : I am not sure if ESD performs the same as energy spectrum. BTW, thanks alot for your answer:)
I am trying to plot the energy spectrum of a signal to look at the for example Normalised energy contained first three harmonics, energy ratio of fundamental to 2nd harmonics etc....
Here I have managed to get the Hanning window FFT amplitude-Hz and power-Hz. But, I have no idea how to get energy-Hz for each frequency components.
Any help is much appreciated !
function [f,Xall_Wnd]=fftplotExxx(time,X_input)
Fs = 20000; % Sampling frequency
x = X_input;
% Fast Fourier Transform
L = length (X_input); % Length of FFT
nfft = 2^nextpow2(L); % Next power of 2 from length of signal
%wave = wave.*hamming(length(wave));
x_HammingWnd = x.*hamming(L);
% Take fft, padding with zeros so that length(X)
%is equal to nfft
Xall_Wnd = fft(x_HammingWnd, nfft)/L; %hamming window fft
% FFT is symmetric, throw away second half
% Take the magnitude of fft of x
mx_Wnd = 2*abs(Xall_Wnd(1:nfft/2+1));
% To get Power of x(t) by sqr of magnitude
m2x_Wnd = mx_Wnd.^2;
% I am Not sure how to get energy spectrum
for i=1:L:nfft-L
E(i) = sum(Xall_Wnd(1:nfft/2+1).^2);
end
% Frequency vector
f = Fs/2*linspace(0,1,nfft/2+1);
% Generate the plot, title and labels.
subplot(2,2,1)
plot(time,X_input);
title('Time Domain')
xlabel('Time(s)')
subplot(2,2,2)
plot(f,m2x_Wnd);
title('Power Spectrum of x(t)');
xlabel('Frequency (Hz)');
ylabel('Normalised Power of fft');
subplot(2,2,3)
plot(f,mx_Wnd);
title('Hamming Window_ Spectrum of x(t)');
xlabel('Frequency (Hz)');
ylabel('Normalised Magunitude of fft');
subplot(2,2,4)
plot(f,E);
title('Energy Spectrum of x(t)');
xlabel('Frequency (Hz)');
ylabel('Energy');
end
Generally you can calculate the spectrum in this way:
h = spectrum.welch('hamming',2048,50);
PSD = psd(h,x(t),'nfft',2048,'fs',Fs);