Audio Library: question on fft1024

Status
Not open for further replies.

superslot

Member
Hi,
I have a silly question on FFT1024 from the Audio Library.

my understanding is that the fft will return the REAL part of the spectrum, and so 512 coeff (N/2), but I would like to know how is the amplitude normalized. is that normalized to 1 ? or to the N/2 samples num?

Also, is there an indication of what level of input signal (Vpp) is required to maximize the fft amplitude?
for example for a 1Khz sinusoidal signal (mic or line input) what is the amplitude needed to max the fft coeff at 1Khz?

a little bit OT: is there a way to compute multiple FFT (series of fft) with partially overlapping windows using the fft1204?
I see the option to delect the sampling window ... but if I'm not wrong that does not help when you need a larger (>1024) FFT size.

thanks
 
It gives the magnitude. Technically, magnitude = sqrt(real^2 + imag^2), so it's not only the real part, but the total energy in each frequency bin.

Data is scaled so a full amplitude sine wave give 1.0. You can get slightly higher than 1.0, due to spectral leakage and numerical round off errors.

The FFT object does 50% overlap, with whatever window you select (Hanning is the default). That's why it gives 86 updates/sec. Each is update is 1024 windowed audio samples (one 43rd of a second), but the update rate is twice that speed because it's doing 50% overlap.
 
Hi Paul,
sorry to ask again but I have another question.. the reason for asking is that I want to compute the SPL level.
For that reason I need to apply the curve (type-A) to the FFT coeff before computing the energy value (i.e. before ^2 and sqrt)

is there a way to retrieve just the FFT coeff or is it possible to hint the FFT with a waiting curve=F(freq), basically a spectrum pre-filter I suppose...

[edit]
I guess I need to modify the source code right after the radix fft, isn't it?

from analyze_fft1024.cpp:
arm_cfft_radix4_q15(&fft_inst, buffer);
// TODO: support averaging multiple copies
for (int i=0; i < 512; i++) {
uint32_t tmp = *((uint32_t *)buffer + i); // real & imag
==> for SPL I guess we need to apply A_TYPE_curve to the fft coeff here.
uint32_t magsq = multiply_16tx16t_add_16bx16b(tmp, tmp);
output = sqrt_uint32_approx(magsq);
}


thanks
 
Last edited:
For that reason I need to apply the curve (type-A) to the FFT coeff before computing the energy value (i.e. before ^2 and sqrt)

checking the A-weighting formula, it seems that the weighting coefficients are real valued and not complex.
In this case you can apply them after estimating the unweighted magnitude estimation ie. after (sqrt(abs(.)^2).
 
Hi all,
there is still one bit of info that is not clear to me. :-(

I'm feeding a 2.2Vpp sine wave (3Khz) to the audio line input.
I've also set the line input level to 2.2Vpp.

In the code I check the peak level and indeed is 1.0 (peak1.read(), and goes down if I reduce the sine wave .. so it works correctly).

but ... when I run the fft I was expecting an amplitude of 16384 (i.e. 1.0 in normalized float) and instead I always see less ... around 5000...
was my expectation wrong about the output of the 3Khz bin in the FFT?
or maybe there some other control that I need to set for the input pipeline?

thanks

[edit]
most likely this is due to the window function ... but shouldn't be the output of the fft compensated for it?
 
Last edited:
but ... when I run the fft I was expecting an amplitude of 16384 (i.e. 1.0 in normalized float) and instead I always see less ... around 5000...
was my expectation wrong about the output of the 3Khz bin in the FFT?

most likely this is due to the window function ... but shouldn't be the output of the fft compensated for it?

As you did not give your code, I have some questions:
- which window did you use?
- what is the sampling frequency?
- how did you read the spectrum?
 
Hi,

I'm using the fft1024 from
https://github.com/PaulStoffregen/Audio/blob/master/analyze_fft1024.cpp
window: the default one (Hanning), sampling frequency is 44100, I'm using the audio board from Paul (sgtl5000 on I2s)

note that if I disable windowing and I feed the same 3Khz sine wave (2.2 Vpp, which means max input amplitute for my lineIN setting) I do get one big spike of ~13000 (almost 1.0 float).
the moment I enable windowing the max value goes down to ~5000 (same input)

Since I'm feeding a pure sine wave (from the signal generator) I did not expect the change since the window is supposed to have an overlapped amplitude of "1" (that is one of the properties of overlapped windowing, isn't it?)

[edit]

basically I'm trying to verify what Paul (I trust his statement btw, I just want to get the same result on the board ... and this is default library code, no mods)
Data is scaled so a full amplitude sine wave give 1.0. You can get slightly higher than 1.0, due to spectral leakage and numerical round off errors.

but when I do the test ... I can see 1.0 ONLY if there is no windowing (fft1024, defaul thanning).
anyone willing to do the same check and prove me wrong please?
 
Last edited:
ok .. here is where I'm so far...

my purpose is to compute the spectrum energy (I want to compute the SPL level from my input LineIN) so I modified a little the FFT1024 to apply the CurveTypeA for SPL.
I did check all the math stage by stage and I think everything is fine, and the drop in amplitude I was mentioning before is due to the window spreading the energy a bit when I feed a "pure" sine wave....

the code I'm running is this one:
Code:
void AudioAnalyzeFFT1024::update(void)
{
	audio_block_t *block;

	block = receiveReadOnly();
	if (!block) return;

	switch (state) {
	case 0:
		blocklist[0] = block;
		state = 1;
		break;
	case 1:
		blocklist[1] = block;
		state = 2;
		break;
	case 2:
		blocklist[2] = block;
		state = 3;
		break;
	case 3:
		blocklist[3] = block;
		state = 4;
		break;
	case 4:
		blocklist[4] = block;
		state = 5;
		break;
	case 5:
		blocklist[5] = block;
		state = 6;
		break;
	case 6:
		blocklist[6] = block;
		state = 7;
		break;
	case 7:
		blocklist[7] = block;
		// TODO: perhaps distribute the work over multiple update() ??
		//       github pull requsts welcome......
		copy_to_fft_buffer(buffer+0x000, blocklist[0]->data);
		copy_to_fft_buffer(buffer+0x100, blocklist[1]->data);
		copy_to_fft_buffer(buffer+0x200, blocklist[2]->data);
		copy_to_fft_buffer(buffer+0x300, blocklist[3]->data);
		copy_to_fft_buffer(buffer+0x400, blocklist[4]->data);
		copy_to_fft_buffer(buffer+0x500, blocklist[5]->data);
		copy_to_fft_buffer(buffer+0x600, blocklist[6]->data);
        copy_to_fft_buffer(buffer+0x700, blocklist[7]->data);
                
        // input samples temporal filtering for overlapping windows
        if(window) 
        { 
        	//apply_window_to_fft_buffer(buffer, window);
            arm_cmplx_mult_real_q15( (q15_t*)buffer, (q15_t*)window, (q15_t*)buffer, 1024);
        }
                
        // FFT1024: 1024 Q15 vector in "buffer", real/img interleaved (r0|i0|r1|i1..)
        arm_cfft_radix4_q15(&fft_inst, buffer);

        // SPL ANSI Curve Filtering: only the first N/2 complex values (REAL signal)
        if(fftCurve)
        {                    
        	arm_cmplx_mult_real_q15( (q15_t*)buffer, (q15_t*)fftCurve, (q15_t*)buffer, 512);    
        }
		
        // Energy: module squared
        for (int i=0; i < 512; i++) {
			uint32_t tmp = *((uint32_t *)buffer + i); // real & imag
            uint32_t magsq = multiply_16tx16t_add_16bx16b(tmp, tmp);
            output[i] = signed_saturate_rshift(magsq,16,1);            
            //output[i] = sqrt_uint32_approx(magsq);
		}

        // FLAG: ready to provide results
		outputflag = true;

        // OVERLAPPING: release 1st part of the samples vector
		release(blocklist[0]);
		release(blocklist[1]);
		release(blocklist[2]);
		release(blocklist[3]);

        // OVERLAPPING: overlap second part of the samples
		blocklist[0] = blocklist[4];
		blocklist[1] = blocklist[5];
		blocklist[2] = blocklist[6];
		blocklist[3] = blocklist[7];

        // OVERLAP: state on the middle stage (50% overlap)
		state = 4;
		break;
	}

}

now I'm seeing another issue: even if I connect the input line in to GND I still see quite a few noise on my FFT... and I think this comes from discontinuities on the imput sample blocks.
If I understand correctly the FFT1024 implementation we load 8 chunks of 128 audio samples each (when they are ready).

so I dumped the first 4 blocks (512 samples) and I do see discontinuities on the 128 boundaries.

is this a known issue? while waiting for the data to load nothing else is running (this is the simple FFT example),
maybe there is some interrupt conflict with the AudioShield or the Serial port I/O ?

just wandering .. because I'm totally clueless on the interrupt mapping for the Audio Shield audioblock4.jpg
 
and adding one more question (sorry Paul ... probably this is a very annoying question...)

is it possible that the noise I'm seeing on the FFT is coming from the board itself?

looking to the schematics the inputs are AC coupled, so ... even if I put the LineIN to GND (to basically mute any input signal) I will still have electrical noise on the ADC, isn't it?

should I put a resistor to "pull down" the input of the ADC right before the caps?

audioshield_input.png
audioshiled_linein_noisefloor.jpg
 
Last edited:
Shorting line in to ground at the header pins is the best way to look at the raw noise of the audio board. The only additional noise (outside of the SGTL5000 chip itself) is then the noise that is picked up in the line-in trace which should be minimal because of the low impedance path to ground. The capacitor will not greatly affect this noise.

If you pull the line-in inputs to ground after the coupling capacitor, this will do three things:
First, it will increase the noise above what it would be without the resistor because of the thermal noise generated by the resistor.
Second, it will introduce a DC error into the ADC readings because the input is no longer floating (it's probably biased to around halfway between the supply rails for best dynamic range).
Third, because the DC level at the SGTL5000 input is closer to ground (how much depends on the resistor value), a lower level signal will cause this voltage to go below ground and possibly damage the chip.

I'm not sure what's causing the glitches you show in the previous post, but those shouldn't affect the noise measurement anyway.
 
thanks whollender...

mmm .. ok ... you are right on all points.

but this is quite a bad news for me .. because if I short to ground the inputs and do an FFT1024 I still see quite a lot of spurious data that finally generate a residual noise SPL of ~36dB.

I can be off in my math too ... but since the FFT1024 coefficients are not null there is for sure a residual SPL ... I was expecting ~20ish ... but >30 is a lot
:-(
 
Here are a couple quick things to try to see if the noise problem is due to some of the code or HW:
1. Use the queue object to read back the raw audio samples, send them to your computer, and run the FFT on your computer, and see what the results look like. This will give you the best possible results, as the 16 bit fixed point arithmetic used in the Teensy for speed will degrade the results (although it shouldn't be too much).

2. Run the FFT on the Teensy with both windowing and your A-weighting functionality turned off.

3. Run the FFT on the Teensy with just windowing turned on.

4. Run the FFT on the Teensy with just the A-weighting turned on.

If the results from #1 are no good (high noise floor), then the noise is probably HW related. If the results from step #2 have a high noise floor, then it's likely that the CMSIS FFT implementation is increasing the noise floor (due to fixed point arithmetic or perhaps they used a faster, less accurate algorithm). If the results from step #3 are bad, then there is something odd going on with the windowing function (keep in mind that the windowing will degrade SNR a bit because it reduces the overall signal power into the FFT). Lastly, if all the results are good except #4, then there is likely an error in the implementation of the A-weighting function.

This should at least give you a start in narrowing down the cause of the noise issue.
 
excellent suggestion whoellender.

I did already 2, 3,4 (actually I always keep the windowing OFF even if it is not he best to do because of the leackage...) and indeed there is still FFT noise,
but not the window and the A waiting seems to affect the final result when LineIN is connected to GND (which is more or less the same order of magnitue)

I will dump the samples (audio input is int16_t Q15, correct? just as a confirm) and run a FFT in octave to compare results.

[EDIT]

Whoellender: it might also be that the fft we use has too much rounding and the noise floor is not good for SPL metering.
I see from the doc online
https://www.keil.com/pack/doc/CMSIS/DSP/html/group___complex_f_f_t.html
that the specific arm_cfft_radix4_q15() has been deprecated...

I'll try to see if I can adapt the code to use the floating point fft, since for SPL metering I don't need high refresh rate for the spectrum computation but I do need to be careful to the computation errors since it's the sum(magnitude_squared) and having a 512 coeff ... it;s just a +-1 or +-2 spread around the FFT_Q15 coeff...
:-(
 
Last edited:
Definitely look at a few blocks of the raw input numbers. If you're getting a lot of analog noise into the signal, the FFT will be "garbage in, garbage out".
 
thanks Paul.
agreed on the GI/GO mode :)

yes ... I have LineIN (both input) wired to GND (!) and I have dumped the input value (see the above pic)
I see noise and I don't know how to reduce it ...

with the lineIN wired to GND I do see a tiny bias: my input are always in the range of -25 (on int16_t input).
I'm not too concerned about DC bias (that will be removed) but there is also high freq noise (goes from -40 to -20) that generates valid CFFT coeff that give me a noise floor.

looking to the board I see a trace under the two input caps for LineIN R and lineIN L.
could it be that that trace is generating analog noise on the audio lineIN ?

I'm really skeptical on this ...
after all I'm setting 2.2 Vpp value for LineIN (and I verified dumping the input value that there is no saturation on my sine wave, with max value of 32768)

I tend to believe this noise floor comes from the FFT (and of course the sums of squares to compute energy)
I still need to test the other two CFFT available on CMSIS... I will add an update as soon as I have a result
 
I tend to believe this noise floor comes from the FFT (and of course the sums of squares to compute energy)
I still need to test the other two CFFT available on CMSIS... I will add an update as soon as I have a result

That should be easy to test:

Generate a 1024 sinusoid. take a forward and inverse FFT, taking care of scaling. Compare output with original sinusoid. They should be identical.

Note also: 'arm_cfft_q15' uses internally parts of 'arm_cfft_radix4_q15', so they are not completely different.
 
I just did some testing on my own. Here is my setup:

1. Computer headphones output connected to Teensy3.1 (with Audio Adaptor Board) Line In Left Signal and Ground.
2. I played a pure tone using http://onlinetonegenerator.com/
3. I am using the example sketch in the audio library FFT.ino

When I play a pure sine tone, I see the corresponding bins with a higher amplitude and the others with a '-'. I modified the codes to display all the bins and it shows '0' instead. So it seems to be working well.
 
Status
Not open for further replies.
Back
Top