I am trying to convert an algorithm initially written with numpy to JavaScript, and I don't manage to reproduce the results from a reverse FFT.
The original algorithm uses numpy.fft.rfft
and numpy.fft.irfft
:
# Get the amplitude
amplitudes = abs(np.fft.rfft(buf, axis=0))
# Randomize phases
ph = np.random.uniform(0, 2*np.pi, (amplitudes.shape[0], 1)) * 1j
amplitudes = amplitudes * np.exp(ph)
# do the inverse FFT
buf = np.fft.irfft(amplitudes, axis=0)
I have found a JavaScript library that seems to do the job for the FFT, and I am using mathjs for the matrix/vector work.
I have made a lot of tries, the thing is I don't know what I should do to imitate numpy.fft.irfft
.
Differences between the 2 FFTs :
The JavaScript FFT function returns a complex output with the negative frequencies, so it contains 2 times more points that the result obtained with
numpy.fft.rfft
. Though the amplitudes in the positive frequencies[0, WIN/2]
seem to match.The JavaScript iFFT returns a complex output, while
numpy.fft.rfft
returns a real output.
ANSWER
Thanks to @hotpaw2 I managed to solve my problem.
The spectrum of a real signal is symmetric and numpy.fft.rfft
returns only the unique components of this spectrum. So for a block of 128 samples, numpy.fft.rfft
returns a spectrum containing 128/2 + 1
values, i.e. 65
values.
Therefore, if I wanted to do the same, I need to discard all the symmetric values from my amplitudes and then apply the phase change.
For the reverse FFT : "to get a real-only output from a full length IFFT, the input has to be complex-conjugate symmetric". So I need to rebuild the spectrum by making the real part symmetric, and the imaginary part in mirror symmetry.
Here is the algorithm :
fft(1, re, im)
amplitudes = math.select(re)
.subset([math.range(0, frameCount / 2)]) // get only the unique part
.abs().done() // input signal is real, so abs value of `re` is the amplitude
// Apply the new phases
re = math.emultiply(math.cos(phases), amplitudes)
im = math.emultiply(math.sin(phases), amplitudes)
// Rebuild `re` and `im` by adding the symetric part
re = math.concat(re, math.subset(re, [symRange]).reverse())
im = math.concat(im, math.select(im).subset([symRange]).emultiply(-1).done().reverse())
// do the inverse FFT
fft(-1, re, im)