Can anyone help me understand this VCV FFT code?

There is a bit of code in the VCV Noise module I’ve been looking at, and there are a few little things I am trying to understand.

Here is the code below with my thoughts added as comments, am I on the right track?

struct InverseAWeightingFFTFilter {
	/* The buffer length is how many bins we want the FFT to have */ 
	static constexpr int BUFFER_LEN = 1024;

	/* Define an input and output buffer.
	   Accumulate the signal in the input buffer each sample.
	   Process the signal using the FFT into the output buffer. */
	alignas(16) float inputBuffer[BUFFER_LEN] = {};
	alignas(16) float outputBuffer[BUFFER_LEN] = {};

	/* frame == sample ? */
	int frame = 0;

	/* The VCV API also has a ComplexFFT, whats the use case for that? */
	dsp::RealFFT fft;

	/* Constructor, set the size of the FFT */
	InverseAWeightingFFTFilter() : fft(BUFFER_LEN) {}

	/* Process function, call each sample passing in the sample time and signal value. */
	float process(float deltaTime, float x) {
		/* Add the current signal value to the input buffer. */
		inputBuffer[frame] = x;
		/* When the buffer is full, then process it with the FFT. */
		if (++frame >= BUFFER_LEN) {
			/* Reset the counter so the samples in the buffer will be overwritten. */
			frame = 0;
			/* Define a frequency buffer that will hold the result of the FFT.
			   Why is this buffer is twice the size of the others? */
			alignas(16) float freqBuffer[BUFFER_LEN * 2];
			/* Calculate the FFT */
			fft.rfft(inputBuffer, freqBuffer);

			/* A loop the same length as the input buffer. */
			for (int i = 0; i < BUFFER_LEN; i++) {
				/* I would guess f is for the frequency of the FFT bin,
				   but I don't understand this calculation */
				float f = 1 / deltaTime / 2 / BUFFER_LEN * i;
				/* amp is going to be a number that we multiple this frequency by  */
				float amp = 0.f;
				/* For this particular transform we only modify FFT bins between 80hz and 20khz */
				if (80.f <= f && f <= 20000.f) {
					/* The next few lines calculate the equation that determines the amp  */
					float f2 = f * f;
					// Inverse A-weighted curve
					amp = ((424.36f + f2) * std::sqrt((11599.3f + f2) * (544496.f + f2)) * (148693636.f + f2)) / (148693636.f * f2 * f2);
				/* The buffer index here is because the frequency buffer is twice the size
				   Is setting both to the same value a loss of precision that doesn't matter?
				   why is the amp multiplied by the buffer length here? */
				freqBuffer[2 * i + 0] *= amp / BUFFER_LEN;
				freqBuffer[2 * i + 1] *= amp / BUFFER_LEN;

			/* Reverse the FFT */
			fft.irfft(freqBuffer, outputBuffer);
		/* FFT has to be calculated over a number of samples
		   but we only need the result for the current sample,
		   if the input buffer is not full then we are returning the value from the previously calculated FFT,
		   does this mean the FFT calculation delays the processed signal by the length of the buffer? */
		return outputBuffer[frame];

Don’t know about your code in particular, and I’m not familiar with this FFT library, and I can’t find the declaration for stuff like freqBuffer. is it 16-bit integers? Anyway - general random comments.

a) I think you are using FFT to do block convolution? If so, I don’t think you are taking into account that you need to do overlap-add or something to avoid the tails of the “impulse response” going out too far.

I made a linear phase graphic equalizer using FFT block convolution in 1997. At first it was terrible, until I read up more on how to do block convolution correctly (I used the old Schafer and Oppenheim book for that).

b) using real-only FFT means at least that you can’t control phase. That limitation would have been fatal for my linear phase EQ, and I wish Bogaudio analyzer could display phase, but it too uses real FFT, I think. For your application you don’t really care about that phase (I guess), hopefully that means it “just works”.

c) My ancient “Colors” generated noise directly with inverse FFT. But for that my algorithm was to set the magnitude of each bin according to the user control, and then use a random phase in each bin. Worked good. Probably not related to anything, but an application where I needed phase control.

d) In colors I did the FFT calcs on a worker thread, because I was worried that to do a “lot” of calculations on the process thread would cause pops and clicks. Seems that a lot of ppl don’t worry about that though.

Also, you are doing that curve calculation over and over. Why not just do it once and keep the results?

Sorry for the confusion, this is not my code…

This is code from within the VCV Noise module, written by Andrew that I am trying to understand, if you click the link in my post you can see it in context.

It’s purpose as far as i understand is to create grey noise by filtering white noise.

1 Like

Ah, of course. Sorry. Comments still apply perhaps. Just remove where is says “you”.

  1. F is the frequency for that bin. (center or corner I don’t know)
  2. The “amp” is how much each bin should be attenuated.
  3. I’m guessing the “/ BUFFER_LEN” is to counteract the amplification that happens in FFT…