Advanced Max: FFTs, Part 3
In the third in our series on using the FFT in Max, spend 45 minutes learning how to visualize the FFT processing you're working with, and further explore the use of various window types in ways that you can see and hear.
Download the patches used in this tutorial.
by Timothy Place on March 14, 2017
Nice tutorial, thanks for that. I thought you would demonstrate using windowing in the [jit.fft] patch... Would still like to see examples of that.
Thanks, Jean-Francois! Great idea to apply windowing to the frames sent to jit.fft.
Gosh, I miss MaxMSP :(
(I've taken a vow to execrate all vernacular pseudo-religious exclamations from my vocabulary; it's just a thing)
Gosh. (and then I did a google research, sigh)
Great series! I've been looking for engaging and accessible ways to explain FFTs to students in my audio processing/design course (which is all Max, all the time, I might add), and these tutorials and patchers are very useful. Thanks!
thx. that's great.
Thanks for a really useful tutorial!
Maybe I just missed it, but what was that little detail you were about to deal with in 42:15?
Ha! Great observation @Alvin!
I was originally planning to come back and introduce the vectral~
object. The benefit is that it would make the drawing of the spectrum less jumpy. I ran out of time and skipped that addition to the patcher.
Cheers!
Thanks, this is one of the best things I've been following along this year!
Hi Tim. Thank you for some great and really informative tutorials, there is so much to be learned here!
I have a question, in this tutorial there is something I can´t comprehend:
In the last part of the tutorial - where you copy/paste parts of your overlapped fft-patch from Tutorial 2, you leave the numbers from that tutorial - that had fft-sizes of 512 samples. So the count-up for the window-indexing is still 512 samples with an overlap/delay of 256 samples (and the window-buffer is set to 512 samples), eventhough the fft-size is 1024 samples big with an overlap of 512 samples.
The spectrum looks neat (with a dc-offset/noisefloor around -70 dB).
-Is match between window-indexing and fft-sizes not that important, or how is it?
I tried to dial in numbers according to your Tutorial 2 setup, so the fft- and window-sizes match with corresponding overlaps etc. The result is a noise floor around -96 dB, but also with quit a lot of unaccounted for noise down there - eventhough for practical applications this would probably be acceptable.
What is the correct way to do this?
Hi,
Thanks for the great tutorial, I'm very happy to find more information about jit.fft, which I believe is missing.
I have some questions troubling me.
I implemented a abstraction in Max to compute the DFT and I compared the results with the DFT implementation in R and they output exactly the same numbers.
Comparing with jit.fft I get different numbers.
Basically I'm testing a 16 sample array with the first sample with value "one" and the remain with zeros (1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0). Just a impulse basically.
By no means, I'm not a Fourier transform wizard, from what I understand FFT is a computational efficient way of computing the DFT, shouldn't the result be the same?
Another intriguing detail that I observed in your patch was that you feed to jit.fft a one plane matrix, and from what is written in the jit.fft help file it seems that requires a two plane matrix because of complex numbers.
If I feed the input matrix with one plane I get a two plane matrix at the output with the same numbers on both planes. Somehow you get the complex numbers (both real and imaginary) from a one plane matrix at the input.
What could I be doing wrong?
What's going on inside jit.fft? The jit.fft help is really poor.
====UPDATE====
OK, I think I figured out the first question, jit.fft normalizes the output by the matrix dimensions, so far this seems what happens for one dimension matrix.
I wish these details were mentioned in the documentation and I wish there was a attribute to turn it off.
Tim is no longer with us 🎩 ...the universe called him away to a better place(a company called, 'Garmin', i believe, is now putting his DSP knowledge to far more well-appreciated use).
Perhaps i can answer some things:
-Is match between window-indexing and fft-sizes not that important, or how is it?
What is the correct way to do this?
Tim did it the correct way, the window-buffer still had to run a full count of 512 in order to pull the full analysis first, before cutting out the upper-half using the overlap of 256(this was all to mimic the 'half-spectrum-flag' of pfft~).
I implemented a abstraction in Max to compute the DFT..I compared the results with the DFT implementation in R and they output exactly the same numbers...
Comparing with jit.fft I get different numbers..
We'd need to see all implementations to understand what went wrong(implementing an abstraction to compute DFT could mean many different things(?)). But also...
Another intriguing detail that I observed in your patch was that you feed to jit.fft a one plane matrix, and from what is written in the jit.fft help file it seems that requires a two plane matrix because of complex numbers. When I feed the input matrix with one plane I get a two plane matrix at the output with the same numbers on both planes.
What could I be doing wrong?
Sounds like you have everything set up right, Tim fed in a one-plane matrix, but he got out a 2-plane matrix same as you(this is why he's able to use 'unjoin' to unpack the 2-element/2-plane list of every cell... you could set jit.cellblock to display all planes at once(send the "plane -1" message) to confirm this is true)...
...around 23:30 - 24:30, this is why he's readjusting for jit.fft's internal normalization with [vexpr $f1 * 2 @scalarmode 1].
—Is match between window-indexing and fft-sizes not that important, or how is it? What is the correct way to do this?
In my case it's not important because I'm not running audio, I just wish to decompose simple signals.
In a practical point of view, from what I'm able to understand, one main difference between DFT and FFT, is that FFT sizes should be 2 to the nth power of Y (2^Y) because of the way how FFT is computed. With DFT it's not so strict.
—Tim did it the correct way, the window-buffer still had to run a full count of 512 in order to pull the full analysis first, before cutting out the upper-half using the overlap of 256(this was all to mimic the 'half-spectrum-flag' of pfft~).
Basically I'm trying to understand what is going on inside this jit.fft.
What was intriguing for me is the fact that somehow he gets is results right, in both Real and Imaginary components (complex numbers).
I did a test with the following input signal:
x[1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
I computed the FFT from a one plane matrix and a two plane matrix and I only get the right results if I use a two plane matrix.
Tim uses a input signal with 512 samples (1 dimension matrix with size 512), if we change the input matrix to two planes the FFT results seem to be a little scaled down.
This scaling is strange to me as well, but the complex numbers results seem to be right.
Moreover, there's some normalisation going on inside jit.fft, which seems to be related with the matrix dimensions, and which explains the scaling down when I changed Tim's matrix to two planes.
I'm attaching a patch that demonstrates what I'm trying to highlight.
Also take into account that by reverse engineering I'm normalising the jit.fft normalisation the way I was expecting to be computed from a DFT.
Another place to verify results:
https://engineering.icalculator.info/discrete-fourier-transform-calculator.html
Try these arrays:
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0
This might be one of the simplest patches to see [jit.fft] in action with a one-dimensional input (like audio buffer). Note that compared to [pfft~], there is no built-in overlap and no windowing (i.e. "rectangular" window).
(On your DFT vs FFT question, you are right - it turns out the FFT algorithm is SO MUCH more powerful than other DFT algorithms, that even if you want to compute a DFT with a given number of samples, you will get the result faster by using the next power of two, padding with zeros, and applying the FFT algorithm. That's why DFT is used more in theoretical texts and FFT appears as soon as you want to actually compute it.)