Old 09-12-2020, 12:36 AM   #1
radiofarmer
Human being with feelings
 
Join Date: Sep 2020
Posts: 12
Default Basic FFT filtering

I've been working with JSFX for the past few months and have so far managed by reading and re-reading (and re-reading, and re-reading...) the JSFX documentation and forum posts by the helpful folks here, but I think I'm finally sufficiently stuck on my current task to finally make an account here and ask for some guidance.

In order to get the hang of the FFT tools, I first made a frequency spectrum analyzer on the basis of the gfx_analyzer JS effect, and I'm now trying to add a simple FFT filter.

This is what I currently have:

Code:
@sample
        // Add current samples to buffer (mono) and update the spectrum display
        abs(sample_buffer[] = 0.5(spl0 + spl1)) > 2^(-120/6) ? update = 1;
	((sample_buffer += 1) > fft_max_size) ? sample_buffer = 0;
	
        // Perform FFT/IFFT for each block (block_size = fft_size/2)
	(i += 1) >= block_size ? (
		(buf_start = sample_buffer - block_size) < 0 ? buf_start += fft_max_size;
                // Load samples into working buffer
		loop(fft_size,
			cur_block[] = buf_start[];
			cur_block += 1;
			(buf_start += 1) >= fft_max_size ? (buf_start -= fft_max_size)
		);
		cur_block -= fft_size;
		fft_real(cur_block, fft_size);
		fft_permute(cur_block, fft_size/2);
		i = 0;
		loop(fft_size/4,
			/*
                        Filter code
                        */
		  i += 2;
		);
		fft_ipermute(cur_block, fft_size/2);
		ifft_real(cur_block, fft_size);
		memcpy(out_buffer, cur_block, block_size);
		i = 0;
		loop(fft_size/2,
			out_buffer[i] *= 0.5/fft_size;
			i += 1;
		);
		i = 0;
	);
	
	spl0=out_buffer[i];
	spl1=out_buffer[i];
First, I want to ask if all the buffer sizes are correct. The documentation says that fft_real takes size samples and returns size/2 complex pairs, but I don't quite understand how ifft_real works in terms of buffer size. I would expect fft_ipermute to produce fft_size/2 outputs in this case, but running ifft_real on fft_size/2 buffer slots produces a delay effect.

Second, I'm hoping someone could provide some pointers on how to perform the filtering. Simply zeroing the bins produced distortion, which as far as I understand should indeed be the case and isn't the result of a coding error. Is the proper approach to convolve the Fourier transform by a buffer containing the frequency response of the desired filter? Is there an effect with a straightforward example of this? (I've been trying to reverse-engineer the peak-following FFT filter, but the additional functionality of that effect makes it harder to parse.) And finally, should I be applying a windowing function to the buffer before I take the FFT? I did this for the spectrum display, but if I'm then going to take the inverse of the windowed FFT, I'd need to correct for the lossiness of the window function, which I'm not sure how to do.

Thanks!
radiofarmer is offline   Reply With Quote
Old 09-12-2020, 10:04 PM   #2
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,770
Default

I once used "fftsplitter" as an example to get stated with doing FFT filters.

In this forum there is a long thread, as I got a lot of help from the community with my efforts at that time. (Supposedly searching the forum on "fftsplitter" should bring it up.)

-Michael
mschnell is offline   Reply With Quote
Old 09-14-2020, 12:57 AM   #3
radiofarmer
Human being with feelings
 
Join Date: Sep 2020
Posts: 12
Default

I had already taken a look at the FFT Splitter effect, and I believe I came across the thread you mentioned. However, I still haven't found an explanation of the methodology of FFT-based filtering. (There're plenty of theoretical explanations to be found, but of course I'm trying to understand the nuances of the specific implementation in JSFX).

As it happens, though, I did succeed in picking out the necessary parts of the autopeakfilter effect and writing a simple FFT filter. But while I at least know how to implement the FFT functions properly, I don't quite understand why it works, specifically in regards to the windowing function. The autopeakfilter effect outputs the sum of the current block (the one that was just filtered) times the window function and the previously filtered block times one minus the window function (i.e. curblock*w + lastblock*(1-w)). Meanwhile, the FFT Splitter effect adds those two buffer values and divides by a term including the sum of the current window function value and the corresponding value at the other (x-axis) end of the window function.

This leaves me wondering 1) what the difference is between these approaches and, more generally, 2) what the theory behind windowing the IFFT output values is. The explanations of applying window functions that I've found on line deals primarily with FFT inputs (i.e. the unprocessed, incoming signals), not outputs.
radiofarmer is offline   Reply With Quote
Old 09-14-2020, 05:14 AM   #4
Justin
Administrator
 
Justin's Avatar
 
Join Date: Jan 2005
Location: NYC
Posts: 15,737
Default

Quote:
Originally Posted by radiofarmer View Post
First, I want to ask if all the buffer sizes are correct. The documentation says that fft_real takes size samples and returns size/2 complex pairs
It does, with the caveat that the first complex pair is a combination of DC (offs=0) and nyquist (offs=1).
Quote:
but I don't quite understand how ifft_real works in terms of buffer size. I would expect fft_ipermute to produce fft_size/2 outputs in this case, but running ifft_real on fft_size/2 buffer slots produces a delay effect.
If you wish to access the bins in order, you should use fft_permute(buffer,fft_size/2), fiddle with the bins, then fft_ipermute(buffer,fft_size/2).

Don't forget that you need to apply 1/(fft_size/2) in gain at some point, too.

Quote:

Second, I'm hoping someone could provide some pointers on how to perform the filtering. Simply zeroing the bins produced distortion, which as far as I understand should indeed be the case and isn't the result of a coding error. Is the proper approach to convolve the Fourier transform by a buffer containing the frequency response of the desired filter? Is there an effect with a straightforward example of this? (I've been trying to reverse-engineer the peak-following FFT filter, but the additional functionality of that effect makes it harder to parse.) And finally, should I be applying a windowing function to the buffer before I take the FFT? I did this for the spectrum display, but if I'm then going to take the inverse of the windowed FFT, I'd need to correct for the lossiness of the window function, which I'm not sure how to do.

Thanks!
Zeroing bins in itself shouldn't produce distortion, if done to every block and the outputs are overlapped.

How the input/output blocks are overlapped should probably depend on what transformation is taking place. If you're doing something linear and constant, you can use the convolution-style overlap, which is, e.g. FFT size=1024:

-512 samples of input, 512 samples of zero
-FFT1024
-permute
-apply 1/1024 gain, apply linear gain to bins, etc
-permute reverse
-IFFT1024
- add B to first 512 samples of FFT output
- save second 512 samples of FFT output to B

If you're doing more complex (dynamic changes to filter), then you'll probably want to use 1024 samples of actual input with a windowing function, and use a windowing function to fade between output blocks (and also power-correct the windowing function, etc)

Hope this helps!

Last edited by Justin; 09-14-2020 at 05:19 AM.
Justin is offline   Reply With Quote
Old 09-14-2020, 07:21 AM   #5
mschnell
Human being with feelings
 
mschnell's Avatar
 
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,770
Default

IIRC, fftsplitter does implement a good overlapping window algorithm.

Some more basics that easily are forgotten:

FFT creates a DC offset for the window it converts. It is recreated when doing reverse FFT. The DC offset is the average (or sum) of the samples.

Hence, if you do a sequence of FFT windows, the sequence of DC offsets is just passed through your filter. And this obviously is the original signal, band-limited to the (double) frequency of the windows (i.e. sample frequency divided by Window size).

Hence the window size defined the lowest frequency that your filter is able to modify.

-Michael

Last edited by mschnell; 09-14-2020 at 01:29 PM.
mschnell is offline   Reply With Quote
Old 09-14-2020, 08:41 AM   #6
sai'ke
Human being with feelings
 
sai'ke's Avatar
 
Join Date: Aug 2009
Location: NL
Posts: 1,457
Default

Geraint made a great STFT template, which is quite helpful in very quickly getting something like this set up: https://forum.cockos.com/showthread.php?t=225955
__________________
[Tracker Plugin: Thread|Github|Reapack] | [Routing Plugin: Thread|Reapack] | [More JSFX: Thread|Descriptions|Reapack]
sai'ke is offline   Reply With Quote
Old 09-20-2020, 01:45 AM   #7
radiofarmer
Human being with feelings
 
Join Date: Sep 2020
Posts: 12
Default

Got busy in the last few days and only how had the time to check back here. Thank you all for the replies, that clears a lot up!

The key part that I was missing turned out to be the overlapping of the transforms, and also my interpretation of the signal flow in the FFT filter in my previous comment was wrong.

And thanks, Saike, for the link. Amazingly, I never came across that post in the course of all my googling.
radiofarmer is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 11:05 PM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.