Related
How can I change the pitch of an imported signal logarithmically / exponentially over time?
Please note that the imported signals that will be used are not single frequencies so a simple sweep or a chirp command will not work since I will be importing vocal audio files, I just created the examples below so they would work and could be tested / show the issues I'm having.
I can change the pitch of a signal over time linearly which works great see part 1 of test code and frequency plot below. Thanks to Sheljohn for the code
%Sweep question part 1
clear all,clf reset,tic,clc
pkg load signal %load packages
%%%----create signal
start_freq=500;
end_freq=20;
fs=22050
len_of_sig=7; %in seconds
t=linspace(0,2*pi*len_of_sig,fs*len_of_sig);
orig_sig1=.8*sin(start_freq*t);
wavwrite([orig_sig1(:)] ,fs,16,strcat('/tmp/0_sig.wav')); % export file
%%%---import signal
[ya, fs, nbitsraw] = wavread('/tmp/0_sig.wav');
orig_total_samples=length(ya); %make this the same length as signal wave
t_import=linspace(0,2*pi*(orig_total_samples/fs),orig_total_samples);
%%%%----Begin linsweep
x = ya(:);
fac=(end_freq-start_freq)/length(x); %linear slope
n = numel(x); % number of timepoints
m = mean(x); % average of the signal
k = transpose(0:n-1); %
h = hilbert( x - m ); % analytic signal
env1 = abs(h); % envelope
sweep=fac*pi*k.^2/(fs); %linearly increasing offset original %alter curve here
p = angle(h) + sweep; % phase + linearly increasing offset original
y = m - imag(hilbert( env1 .* sin(p) )); % inverse-transform
wavwrite([y(:)] ,fs,16,strcat('/tmp/0_sweep.wav')); % export file
%%%----------Used for plotting
z = hilbert(y);
instfreq = fs/(2*pi)*diff(unwrap(angle(z))); %orginal
t_new=t_import/(2*pi); %converts it to seconds
plot(t_new(2:end),instfreq,'-r')
xlabel('Time (secnds)')
ylabel('Frequency (Hz)')
grid on
title('Instantaneous Frequency')
Issues with the code I have below are:
1) The frequency doesn't start or end at the correct frequency.
2) It doesn't have the correct slopes
I believe it has to do with the variables fac and sweep I'm just not sure how to calculate them correctly.
fac=log(start_freq/end_freq)/length(x); %slope
sweep=-(start_freq)*exp(fac*k); %alter curve here
-
%-----------------Sweep question part 2
clear all,clf reset,tic,clc
pkg load signal %load packages
%%%----create signal
start_freq=500;
end_freq=20;
fs=22050
len_of_sig=7; %in seconds
t=linspace(0,2*pi*len_of_sig,fs*len_of_sig);
orig_sig1=.8*sin(start_freq*t);
wavwrite([orig_sig1(:)] ,fs,16,strcat('/tmp/0_sig.wav')); % export file
%%%---import signal
[ya, fs, nbitsraw] = wavread('/tmp/0_sig.wav');
orig_total_samples=length(ya); %make this the same length as signal wave
t_import=linspace(0,2*pi*(orig_total_samples/fs),orig_total_samples);
%%%%----Begin linsweep
x = ya(:);
fac=log(start_freq/end_freq)/length(x); %slope
n = numel(x); % number of timepoints
m = mean(x); % average of the signal
k = transpose(0:n-1); %
h = hilbert( x - m ); % analytic signal
env1 = abs(h); % envelope
sweep=-(start_freq)*exp(fac*k); %alter curve here
p = angle(h) + sweep; % phase + increasing offset
y = m - imag(hilbert( env1 .* sin(p) )); % inverse-transform
wavwrite([y(:)] ,fs,16,strcat('/tmp/0_sweep.wav')); % export file
%%%----------Used for plotting
z = hilbert(y);
instfreq = fs/(2*pi)*diff(unwrap(angle(z))); %orginal
t_new=t_import/(2*pi); %converts it to seconds
plot(t_new(2:end),instfreq,'-r')
xlabel('Time (seconds)')
ylabel('Frequency (Hz)')
grid on
title('Instantaneous Frequency')
The slopes I'm trying to get are when the start frequency starts at 500hz and goes to 20hz. And when the start frequency starts at 20hz and it goes to 500hz. See plots below: Note: These frequency will change so I'm trying to get the correct formula / equation that will calculate these slopes when needed.
Ps: I'm using Octave 4.0 which is similar to Matlab.
Please note that the imported signals that will be used are not single frequencies so a simple sweep or a chirp command will not work since I will be importing vocal audio files, I just created the examples below so they would work and could be tested / show the issues I'm having.
I can get the sweep to look like the plot you are interested in by making the following changes to your code. Some of them are just cosmetic for my sake (e.g. I like my time variables to remain in units of seconds throughout).
Relevant changes:
From:
t=linspace(0,2*pi*len_of_sig,fs*len_of_sig);
orig_sig1=.8*sin(start_freq*t);
fac=log(start_freq/end_freq)/length(x); %slope
To:
t=linspace(0,len_of_sig,fs*len_of_sig);
orig_sig1=0.8*sin(start_freq*t*2*pi);
fac=log(end_freq/start_freq)/length(x);
sweep=(start_freq*2*pi/fs)*exp(fac*k); %alter curve here
Here was some other changes I made,
y = env1.*sin(p);
% and later for consistency
t_import=linspace(0,orig_total_samples/fs,orig_total_samples);
t_new=t_import; %t is seconds
The fac, in my mind, is going to be the difference from your start and end, so it would be: log(endFreq)-log(startFreq) or log(endFreq/startFreq) with the additional normalization for the length. This can be flipped with a negative sign in front.
One issue with the sweep may be happening when you use it to calculate p=angle(h)+sweep; where angle(h) is in radians.
The radians vs Hz units issue may be causing some of the difficulty.
I have an instrument which produces roughly sinusoidal data, but with frequency varying slightly in time. I am using MATLAB to prototype some code to characterize the time dependence, but I'm running into some issues.
I am generating an idealized approximation of my data, I(t) = sin(2 pi f(t) t), with f(t) variable but currently tested as linear or quadratic. I then implement a sliding Hamming window (of width w) to generate a set of Fourier transforms F[I(t), t'] corresponding to the data points in I(t), and each F[I(t), t'] is fit with a Gaussian to more precisely determine the peak location.
My current MATLAB code is:
fs = 1000; %Sample frequency (Hz)
tlim = [0,1];
t = (tlim(1)/fs:1/fs:tlim(2)-1/fs)'; %Sample domain (t)
N = numel(t);
f = #(t) 100-30*(t-0.5).^2; %Frequency function (Hz)
I = sin(2*pi*f(t).*t); %Sample function
w = 201; %window width
ww=floor(w/2); %window half-width
for i=0:2:N-w
%Take the FFT of a portion of I, convolved with a Hamming window
II = 1/(fs*N)*abs(fft(I((1:w)+i).*hamming(w))).^2;
II = II(1:floor(numel(II)/2));
p = (0:fs/w:(fs/2-fs/w))';
%Find approximate FFT maximum
[~,maxIx] = max(II);
maxLoc = p(maxIx);
%Fit the resulting FFT with a Gaussian function
gauss = #(c,x) c(1)*exp(-(x-c(2)).^2/(2*c(3)^2));
op = optimset('Display','off');
mdl = lsqcurvefit(gauss,[max(II),maxLoc,10],p,II,[],[],op);
%Generate diagnostic plots
subplot(3,1,1);plot(p,II,p,gauss(mdl,p))
line(f(t(i+ww))*[1,1],ylim,'color','r');
subplot(3,1,2);plot(t,I);
line(t(1+i)*[1,1],ylim,'color','r');line(t(w+i)*[1,1],ylim,'color','r')
subplot(3,1,3);plot(t(i+ww),f(t(i+ww)),'b.',t(i+ww),mdl(2),'r.');
hold on
xlim([0,max(t)])
drawnow
end
hold off
My thought process is that the peak location in each F[I(t), t'] should be a close approximation of the frequency at the center of the window which was used to produce it. However, this does not seem to be the case, experimentally.
I have had some success using discrete Fourier analysis for engineering problems in the past, but I've only done coursework on continuous Fourier transforms--so there may be something obvious that I'm missing. Also, this is my first question on StackExchange, so constructive criticism is welcome.
So it turns out that my problem was a poor understanding of the mathematics of the sine function. I had assumed that the frequency of the wave was equal to whatever was multiplied by the time variable (e.g. the f in sin(ft)). However, it turns out that the frequency is actually defined by the derivative of the entire argument of the sine function--the rate of change of the phase.
For constant f the two definitions are equal, since d(ft)/dt = f. But for, say, f(t) = sin(t):
d(f(t)t)/dt = d(sin(t) t)/dt = t cos(t) + sin(t)
The frequency varies as a function very different from f(t). Changing the function definition to the following fixed my problem:
f = #(t) 100-30*(t-0.5).^2; %Frequency function (Hz)
G = cumsum(f(t))/fs; %Phase function (Hz)
I = sin(2*pi*G); %Sampling function
I have generated three identical waves with a phase shift in each. For example:
t = 1:10800; % generate time vector
fs = 1; % sampling frequency (seconds)
A = 2; % amplitude
P = 1000; % period (seconds), the time it takes for the signal to repeat itself
f1 = 1/P; % number of cycles per second (i.e. how often the signal repeats itself every second).
y1 = A*sin(2*pi*f1*t); % signal 1
phi = 10; % phase shift
y2 = A*sin(2*pi*f1*t + phi); % signal 2
phi = 15; % phase shift
y3 = A*sin(2*pi*f1*t + phi); % signal 3
YY = [y1',y2',y3'];
plot(t,YY)
I would now like to use a method for detecting this phase shift between the waves. The point of doing this is so that I can eventually apply the method to real data and identify phase shifts between signals.
So far I have been thinking of computing the cross spectra between each wave and the first wave (i.e. without the phase shift):
for i = 1:3;
[Pxy,Freq] = cpsd(YY(:,1),YY(:,i));
coP = real(Pxy);
quadP = imag(Pxy);
phase(:,i) = atan2(coP,quadP);
end
but I'm not sure if this makes any sense.
Has anyone else done something similar to this? The desired outcome should show a phase shift at 10 and 15 for waves 2 and 3 respectively.
Any advice would be appreciated.
There are several ways that you can measure the phase shift between signals. Between your response, the comments below your response, and the other answers, you've gotten most of the options. The specific choice of technique is usually based on issues such as:
Noisy or Clean: Is there noise in your signal?
Multi-Component or Single-Component: Are there more than one type of signal within your recording (multiple tones at multiple frequencies moving in different directions)? Or, is there just a single signal, like in your sine-wave example?
Instantaneous or Averaged: Are you looking for the average phase lag across your entire recording, or are you looking to track how the phase changes throughout the recording?
Depending on your answer to these questions, you could consider the following techniques:
Cross-Correlation: Use the a command like [c,lag]=xcorr(y1,y2); to get the cross-correlation between the two signals. This works on the original time-domain signals. You look for the index where c is maximum ([maxC,I]=max(c);) and then you get your lag value in units of samples lag = lag(I);. This approach gives you the average phase lag for the entire recording. It requires that your signal of interest in the recording be stronger than anything else in your recording...in other words, it is sensitive to noise and other interference.
Frequency Domain: Here you convert your signals into the frequency domain (using fft or cpsd or whatever). Then, you'd find the bin that corresponds to the frequency that you care about and get the angle between the two signals. So, for example, if bin #18 corresponds to your signal's frequency, you'd get the phase lag in radians via phase_rad = angle(fft_y1(18)/fft_y2(18));. If your signals have a constant frequency, this is an excellent approach because it naturally rejects all noise and interference at other frequencies. You can have really strong interference at one frequency, but you can still cleanly get your signal at another frequency. This technique is not the best for signals that change frequency during the fft analysis window.
Hilbert Transform: A third technique, often overlooked, is to convert your time-domain signal into an analytic signal via the Hilbert transform: y1_h = hilbert(y1);. Once you do this, your signal is a vector of complex numbers. A vector holding a simple sine wave in the time domain will now be a vector of complex numbers whose magnitude is constant and whose phase is changing in sync with your original sine wave. This technique allows you to get the instantaneous phase lag between two signals...it's powerful: phase_rad = angle(y1_h ./ y2_h); or phase_rad = wrap(angle(y1_h) - angle(y2_h));. The major limitation to this approach is that your signal needs to be mono-component, meaning that your signal of interest must dominate your recording. Therefore, you may have to filter out any substantial interference that might exist.
For two sinusoidal signal the phase of the complex correlation coefficient gives you what you want. I can only give you an python example (using scipy) as I don't have a matlab to test it.
x1 = sin( 0.1*arange(1024) )
x2 = sin( 0.1*arange(1024) + 0.456)
x1h = hilbert(x1)
x2h = hilbert(x2)
c = inner( x1h, conj(x2h) ) / sqrt( inner(x1h,conj(x1h)) * inner(x2h,conj(x2h)) )
phase_diff = angle(c)
There is a function corrcoeff in matlab, that should work, too (The python one discard the imaginary part). I.e. c = corrcoeff(x1h,x2h) should work in matlab.
The Matlab code to find relative phase using cross-correlation:
fr = 20; % input signal freq
timeStep = 1e-4;
t = 0:timeStep:50; % time vector
y1 = sin(2*pi*t); % reference signal
ph = 0.5; % phase difference to be detected in radians
y2 = 0.9 * sin(2*pi*t + ph); % signal, the phase of which, is to be measured relative to the reference signal
[c,lag]=xcorr(y1,y2); % calc. cross-corel-n
[maxC,I]=max(c); % find max
PH = (lag(I) * timeStep) * 2 * pi; % calculated phase in radians
>> PH
PH =
0.4995
With the correct signals:
t = 1:10800; % generate time vector
fs = 1; % sampling frequency (seconds)
A = 2; % amplitude
P = 1000; % period (seconds), the time it takes for the signal to repeat itself
f1 = 1/P; % number of cycles per second (i.e. how often the signal repeats itself every second).
y1 = A*sin(2*pi*f1*t); % signal 1
phi = 10*pi/180; % phase shift in radians
y2 = A*sin(2*pi*f1*t + phi); % signal 2
phi = 15*pi/180; % phase shift in radians
y3 = A*sin(2*pi*f1*t + phi); % signal 3
The following should work:
>> acos(dot(y1,y2)/(norm(y1)*norm(y2)))
>> ans*180/pi
ans = 9.9332
>> acos(dot(y1,y3)/(norm(y1)*norm(y3)))
ans = 0.25980
>> ans*180/pi
ans = 14.885
Whether or not that's good enough for your "real" signals, only you can tell.
Here is the little modification of your code: phi = 10 is actually in degree, then in sine function, phase information is mostly expressed in radian,so you need to change deg2rad(phi) as following:
t = 1:10800; % generate time vector
fs = 1; % sampling frequency (seconds)
A = 2; % amplitude
P = 1000; % period (seconds), the time it takes for the signal to repeat itself
f1 = 1/P; % number of cycles per second (i.e. how often the signal repeats itself every second).
y1 = A*sin(2*pi*f1*t); % signal 1
phi = deg2rad(10); % phase shift
y2 = A*sin(2*pi*f1*t + phi); % signal 2
phi = deg2rad(15); % phase shift
y3 = A*sin(2*pi*f1*t + phi); % signal 3
YY = [y1',y2',y3'];
plot(t,YY)
then using frequency domain method as mentioned chipaudette
fft_y1 = fft(y1);
fft_y2 = fft(y2);
phase_rad = angle(fft_y1(1:end/2)/fft_y2(1:end/2));
phase_deg = rad2deg(angle(fft_y1(1:end/2)/fft_y2(1:end/2)));
now this will give you a phase shift estimate with error = +-0.2145
If you know the frequency and just want to find the phase, rather than use a full FFT, you might want to consider the Goertzel algorithm, which is a more efficient way to calculate the DFT for a single frequency (an FFT will calculate it for all frequencies).
For a good implementation, see: https://www.mathworks.com/matlabcentral/fileexchange/35103-generalized-goertzel-algorithm and https://asp-eurasipjournals.springeropen.com/track/pdf/10.1186/1687-6180-2012-56.pdf
If you use an AWGN signal with delay and apply your method it works, but if you are using a single tone frequency estimation will not help you. because there is no energy in any other frequency but the tone. You better use cross-correlation in the time domain for this - it will work better for a fixed delay. If you have a wideband signal you can use subbands domain and estimate the phase from that (it is better than FFT due to low cross-frequency dependencies).
I have a question while computing the spectrum of a time series in Matlab. I have read the documentations concerning 'fft' function. However I have seen two ways of implementation and both wgive me different results. I would appreciate to have some answer about this difference:
1st Method:
nPoints=length(timeSeries);
Time specifications:
Fs = 1; % samples per second
Fs = 50;
freq = 0:nPoints-1; %Numerators of frequency series
freq = freq.*Fs./nPoints;
% Fourier Transform:
X = fft(timeSeries)/nPoints; % normalize the data
% find find nuquist frequency
cutOff = ceil(nPoints./2);
% take only the first half of the spectrum
X = abs(X(1:cutOff));
% Frequency specifications:
freq = freq(1:cutOff);
%Plot spectrum
semilogy(handles.plotLoadSeries,freq,X);
2nd Method:
NFFT = 2^nextpow2(nPoints); % Next power of 2 from length of y
Y = fft(timeSeries,NFFT)/nPoints;
f = 1/2*linspace(0,1,NFFT/2+1);
% % Plot single-sided amplitude spectrum.
% plot(handles.plotLoadSeries, f,2*abs(Y(1:NFFT/2+1)))
semilogy(handles.plotLoadSeries,f,2*abs(Y(1:NFFT/2+1)));
I thought that it is not necessary to use 'nextpow' function in 'fft' function in Matlab. Finally, which is the good one?
THanks
The short answer: you need windowing for spectrum analysis.
Now for the long answer... In the second approach, you are using an optimised FFT algorithm useful when the length of the input vector is a power of two. Let's assume that your original signal has 401 samples (as in my example below) from an infinitely long signal; nextpow2() will give you NFFT=512 samples. When you feed the shorter, 401-sample signal into the fft() function, it is implicitly zero-padded to match the requested length of 512 (NFFT). But (here comes the tricky part): zero-padding your signal is equivalent to multiplying an infinitely long signal by a rectangular function, an operation that in the frequency domain translates to a convolution with a sinc function. This would be the reason behind the increased noise floor at the bottom of your semilogarithmic plot.
A way to avoid this noise increase is to create manually the 512-sample signal you want to feed into fft(), using a smoother window function instead of the default rectangular one. Windowing means just multiplying your signal by a tapered, symmetric one. There are tons of literature on choosing a good windowing function, but a typically accurate one with low sidelobes (low noise increase) is the Hamming function, implemented in MATLAB as hamming().
Here is a figure illustrating the issue (in the frequency domain and time domain):
...and the code to generate this figure:
clear
% Create signal
fs = 40; % sampling freq.
Ts = 1/fs; % sampling period
t = 0:Ts:10; % time vector
s = sin(2*pi*3*t); % original signal
N = length(s);
% FFT (length not power of 2)
S = abs(fft(s)/N);
freq = fs*(0:N-1)/N;
% FFT (length power of 2)
N2 = 2^nextpow2(N);
S2 = abs(fft(s, N2)/N2);
freq2 = fs*(0:N2-1)/N2;
t2 = (0:N2-1)*Ts; % longer time vector
s2 = [s,zeros(1,N2-N)]; % signal that was implicitly created for this FFT
% FFT (windowing before FFT)
s3 = [s.*hamming(N).',zeros(1,N2-N)];
S3 = abs(fft(s3, N2)/N2);
% Frequency-domain plot
figure(1)
subplot(211)
cla
semilogy(freq,S);
hold on
semilogy(freq2,S2,'r');
semilogy(freq2,S3,'g');
xlabel('Frequency [Hz]')
ylabel('FFT')
grid on
legend( 'FFT[401]', 'FFT[512]', 'FFT[512] with windowing' )
% Time-domain plot
subplot(212)
cla
plot(s)
hold on
plot(s3,'g')
xlabel('Index')
ylabel('Amplitude')
grid on
legend( 'Original samples', 'Windowed samples' )
I am very interested to know how the top right figure in :http://en.wikipedia.org/wiki/Spectrogram
is generated (the script) and how to analyse it i.e what information does it convey?I would appreciate a simplified answer with minimum mathematical jargons. Thank you.
The plot shows time along the horizontal axis, and frequency along the vertical axis. With pixel color showing the intensity of each frequency at each time.
A spectrogram is generated by taking a signal and chopping it into small time segments, doing a Fourier series on each segment.
here is some matlab code to generate one.
Notice how plotting the signal directly, it looks like garbage, but plotting the spectrogram, we can clearly see the frequencies of the component signals.
%%%%%%%%
%% setup
%%%%%%%%
%signal length in seconds
signalLength = 60+10*randn();
%100Hz sampling rate
sampleRate = 100;
dt = 1/sampleRate;
%total number of samples, and all time tags
Nsamples = round(sampleRate*signalLength);
time = linspace(0,signalLength,Nsamples);
%%%%%%%%%%%%%%%%%%%%%
%create a test signal
%%%%%%%%%%%%%%%%%%%%%
%function for converting from time to frequency in this test signal
F1 = #(T)0+40*T/signalLength; #frequency increasing with time
M1 = #(T)1-T/signalLength; #amplitude decreasing with time
F2 = #(T)20+10*sin(2*pi()*T/signalLength); #oscilating frequenct over time
M2 = #(T)1/2; #constant low amplitude
%Signal frequency as a function of time
signal1Frequency = F1(time);
signal1Mag = M1(time);
signal2Frequency = F2(time);
signal2Mag = M2(time);
%integrate frequency to get angle
signal1Angle = 2*pi()*dt*cumsum(signal1Frequency);
signal2Angle = 2*pi()*dt*cumsum(signal2Frequency);
%sin of the angle to get the signal value
signal = signal1Mag.*sin(signal1Angle+randn()) + signal2Mag.*sin(signal2Angle+randn());
figure();
plot(time,signal)
%%%%%%%%%%%%%%%%%%%%%%%
%processing starts here
%%%%%%%%%%%%%%%%%%%%%%%
frequencyResolution = 1
%time resolution, binWidth, is inversly proportional to frequency resolution
binWidth = 1/frequencyResolution;
%number of resulting samples per bin
binSize = sampleRate*binWidth;
%number of bins
Nbins = ceil(Nsamples/binSize);
%pad the data with zeros so that it fills Nbins
signal(Nbins*binSize+1)=0;
signal(end) = [];
%reshape the data to binSize by Nbins
signal = reshape(signal,[binSize,Nbins]);
%calculate the fourier transform
fourierResult = fft(signal);
%convert the cos+j*sin, encoded in the complex numbers into magnitude.^2
mags= fourierResult.*conj(fourierResult);
binTimes = linspace(0,signalLength,Nbins);
frequencies = (0:frequencyResolution:binSize*frequencyResolution);
frequencies = frequencies(1:end-1);
%the upper frequencies are just aliasing, you can ignore them in this example.
slice = frequencies<max(frequencies)/2;
%plot the spectrogram
figure();
pcolor(binTimes,frequencies(slice),mags(slice,:));
The inverse Fourier transform of the fourierResult matrix, will return the original signal.
Just to add to Suki's answer, here is a great tutorial that walks you through, step by step, reading Matlab spectrograms, touching on only enough math and physics to explain the main concepts intuitively:
http://www.caam.rice.edu/~yad1/data/EEG_Rice/Literature/Spectrograms.pdf