I am trying to time-shift a signal using the FFT, however I have encountered some strange effects that depend on the size of the time-shift. I need to be able to shift the time by an arbitrary amount - i.e. floating-point time. I am using the method from this thread:
Here is my code:
import numpy as np
import matplotlib.pyplot as plt
f1 = 1.8
f2 = 2.6
#try tDelay = .02002 and tDelay = .0205
tDelay = .0205 #seconds
samples = 1024 #number of samples in the time interval
tstart = 0.0
tend = 1.0
# create a waveform to use for the time shifting
samplePeriod = (tend - tstart) / (samples)
print("\nThe sampling period is %f seconds" % samplePeriod)
print("The time delay is %f seconds" % tDelay)
tDelayInSamples = tDelay / samplePeriod
print("The time delay in samples is %f samples" % tDelayInSamples)
timeList = np.linspace(tstart, tend, samples)
waveform = np.sin(2 * np.pi * f1 * timeList) + np.sin(2 * np.pi * f2 * timeList)
# do the time shifting
fftOut = np.fft.fft(waveform)
N = fftOut.shape[0]
k = np.linspace(0, N-1, N)
phaseShiftFunction = np.exp((-2*np.pi*1j*k*tDelayInSamples)/(N))
fftWithDelay = np.multiply(fftOut, phaseShiftFunction)
waveform2 = np.fft.ifft(fftWithDelay)
plots = 1
plt.subplot(plots, 1, 1)
plt.plot(waveform)
plt.plot(waveform2)
plt.show()
If you run the above code using tDelay like .02002 and .0205, you will see that the recovered signal is drastically different. I don't understand why this is the case, or what to do about it.
Example of good time-shift (tDelay = .0205):
Example of bad time-shift (tDelay = .02007):
EDIT:
After considerable effort I believe I have solved this problem. There are 2 key insights here:
When you do a fractional time shift, you have to do an fftshift on the array of phase shift values, to make it symmetric. (This is not needed for integer-sample time shifts). See this thread.
After multiplying the FFTed data with the phase shift values, if you immediately do the IFFT, you will find that the data is correctly time-shifted, but the waveform is rotated at some angle in the complex plane. This angle depends on the sample fraction corresponding to the time shift. For example, a time shift corresponding to N+.5 samples will be entirely imaginary (i.e. rotated by $\pi/2$). To rotate back to the real axis, add an additional phase shift (before doing IFFT): $e^{\pi i D}$ (where D is the time-shift in samples).
So just to be super clear, the process is:
- Take the FFT
- Construct the phase shift $e^{\frac{-2\pi i k D}{N} + \pi i D}$ (where k=sample number, D=time shift in samples, N=sample length of the FFT)
- Do FFTshift on the phase shift coefficients calculated in #2
- Now multiply the FFT data with the phase shift coefficients
- Do the IFFT
Example Code:
import numpy as np
import matplotlib.pyplot as plt
f1 = 12.8
f2 = 22.6
samples = 1024
tDelay = .00938
tstart = 0.0
tend = 1.0
# 0. Example waveform to demonstrate the time shift
timeList = np.linspace(tstart, tend, samples)
waveform = np.sin(2 * np.pi * f1 * timeList) + 1*np.sin(2 * np.pi * f2 * timeList)
# 1. Take the FFT
fftData = np.fft.fft(waveform)
# 2. Construct the phase shift
samplePeriod = (tend - tstart) / (samples)
tDelayInSamples = tDelay / samplePeriod
N = fftData.shape[0]
k = np.linspace(0, N-1, N)
timeDelayPhaseShift = np.exp(((-2*np.pi*1j*k*tDelayInSamples)/(N)) + (tDelayInSamples*np.pi*1j))
# 3. Do the fftshift on the phase shift coefficients
timeDelayPhaseShift = np.fft.fftshift(timeDelayPhaseShift)
# 4. Multiply the fft data with the coefficients to apply the time shift
fftWithDelay = np.multiply(fftData, timeDelayPhaseShift)
# 5. Do the IFFT
shiftedWaveform = np.fft.ifft(fftWithDelay)
print("\nThe sampling period is %f seconds" % samplePeriod)
print("The time delay is %f seconds" % tDelay)
print("The time delay in samples is %f samples" % tDelayInSamples)
print("The correction phase shift is %f pi" % (tDelayInSamples))
plots = 1
plt.subplot(plots, 1, 1)
plt.plot(waveform)
plt.plot(shiftedWaveform)
plt.show()
I welcome any additional insight someone might have for better ways to do this, or if I have overlooked anything!