How can I convert a time signal (audio) into the frequency domain, multiply frequency amplitudes by a vector of scalars, and convert it back into the time domain, without messing up timing information (ringing?)?
Some context:
I am calibrating ultrasonic speakers. I have a frequency roll-off curve of the attenuation dB for each frequency, from a reference point. I now want to compensate my output using this curve. Here is an example that was gathered experimentally:
I can then multiply my output signal in the frequency domain by this attenuation vector, and convert back into the time domain. For the most part this works, however, there is some extra energy at the wrong times, and I am not sure why. Here is what it looks like: Here is some python code to demonstrate what I have. To make this a compilable example I try to approximate the attenuation vector with a sawtooth for variation.
import numpy as np
from scipy.signal import chirp, sawtooth
def smooth(x,window_len=11):
"""smooth the data using a window with requested size."""
s=np.r_[x[window_len-1:0:-1],x,x[-1:-window_len:-1]]
w = np.hanning(window_len)
y=np.convolve(w/w.sum(),s,mode='valid')
return y[window_len/2:len(y)-window_len/2]
fs = 5e5
duration = 0.2
npts = duration*fs
t = np.arange(npts).astype(float)/fs
# my output stimulus
x = chirp(t, f0=5000, f1=1e5, t1=duration)
X = np.fft.rfft(x)
# stand-in attenuation vector
variation = sawtooth(np.arange(npts/2 + 1)/500)
attendB = np.linspace(0, 30, npts/2 + 1) + variation
# attempt to minimize ringing?
attendB = smooth(attendB)
H = 10**((attendB).astype(float)/20)
# signal adjusted to calibration
A = X * H
a = np.fft.irfft(A)
# ==== plot ====
import matplotlib.pyplot as plt
plt.subplot(211)
plt.specgram(x, Fs=fs)
plt.title('original')
plt.subplot(212)
plt.specgram(a, Fs=fs)
plt.title('adjusted')
plt.show()
I can eliminate the extra energy by increasing the smoothing. However, this also reduces the accuracy of the calibration, as assesed by comparing the desired and recieved frequency response. Is this a trade-off I have to deal with, or am I going about this the wrong way? Alternatively, is it a feasible solution to implement this as a filter in the time domain?