How do I calculate peak amplitude of the signal components after zero padding and FFT?

I am learning about DFT and trying to apply it to some audio processing. I am new to DSP but experienced in programming and have some background in math and physics. The FFT algorithm I use (lomontFFT) doesn't have 1/N scaling in front of the DFT sum (ie. it's defined just like on wikipedia DFT page). I will use simple example to describe my issue.

Let's say I have have 1024 samples of the pure 100Hz sine signal with peak amplitude of 1, and let the sample rate be 10240Hz. So that means that frequency resolution is 10240Hz/1024 = 10Hz. So when I perform FFT I get peak in 10th frequency bin which is correct. (since the signal is real I am only looking at the first half of the spectrum). Now, the magnitude of this peak in 10th bin is 512. How should I interpret this value? Is this magnitude somehow related to the energy of the 100Hz sine wave component in the signal?

Now, how can I get a spectrum to show amplitudes? I'd like to see what components are contained in the signal and what are their amplitudes. So in this example I should have one component at the 10th frequency bin with the value of 1 which is equal to the amplitude. I know I can divide the current value I got by 512 which seems to be the N/2. Is this correct way to calculate it?

If so, what about zero padding? If I zero-pad the same signal with additional 1024 zeros now I have total of 2048 samples. This means that frequency resolution is now 5Hz and my sine component is in the 20th bin. The magnitude is again 512, however now if I divide it by N/2=2048/2=1024 I get 0.5 instead of 1 for the peak amplitude. It seems like I should not count zero padded samples in this scaling? But what about if I just get the signal from somewhere and I don't know if signal is zero padded?

Also it seems that zero padding changes magnitudes in other bins. So in the first case without zero padding I get magnitude of 512 in the 10th bin and all other magnitudes are nearly zero (eg. values of the order 10^-10). But in the second case with zero padding, beside the magnitude value of 512 in the 20th bin I also get large magnitudes in bins around it (eg. 19th bin has magnitude of 334 and 21th bin has magnitude of 318 etc...). I guess these are side-lobes of spectral leaking but why are they showing up if I only have one sine frequency of 100Hz and frequency resolution is 5Hz so it falls exactly in the 20th bin?

I wish I could show some plots but I am programming this in C# console application so I can't provide visuals.

Note: I am using "frequency resolution" term here to denote "frequency bin spacing", I am aware that zero-padding doesn't increases frequency resolution since it doesn't add any new information to the signal.

It looks to me like your frequency resolution is sufficient and you are getting the correct value of the peak magnitude in your FFT but your scaling factor is incorrect.

You are right in dividing the magnitude of your FFT peak by N (1024 in your case). This number however, divides the power across the positive and negative sides of the frequency spectrum.

If you only want to look at one side of the FFT, you should multiply your peak magnitude by 2/N to get the correct amplitude that you are expecting. See this answer for another example.

e.g.

512 * 2 / 1024 = 1 (the answer that you were expecting)


What you have observed is why I prefer a $1/N$ normalization factor. If your signal is a pure tone, it is very well behaved in a DFT. For a frequency which is a whole number of cycles within your sample frame, the magnitude of the corresponding bin will be $1/2$. This is due entirely to the fact that a sinusoid is the average of two complex signals. The most straightforward way to see it is in the exponential definition of the cosine function:

$$\cos( \theta ) = \frac{ e^{ i\theta } + e^{ -i\theta } }{2}$$

This also explains why the upper half of the DFT is the complex conjugate mirror of the lower half for real valued signals.

When your pure tone is not a whole integer number of cycles in the sample frame a phenomenon known as "leakage" occurs. This is not a flaw, it is how the DFT works. You can find the equation for leakage values in my blog article: DFT Bin Value Formulas for Pure Real Tones. As far as I know, this is the only place you will find these exact formulas.

You will find a bunch of articles concerning the DFT in my blog. I recommend you start at the beginning and read them all.

Please have a look at the following MATLAB / OCTAVE code that performs a DFT analysis of the windowed sine wave including adjustable zero padding. As you can see (without any theoretical analysis) the scaling factor is not related to the zero padded final length but the original nonzero samples length. Furthermore you can see that MATLAB's built in FFT also uses no $1/N$ weighting, as apparent from the explicitly computed result. If your DFT/FFT routine uses any weight $K$ then you should only consider that $K$ in addition. Note that in order to make the plots clear, I've chosen the signal frequency and sampling frequency in rational proportion so that the peak of the DFT coincides with the actual sine wave frequeny. Otherwise there will be further changes in the DFT peak amplitude due to the shift in location of the samples of DFT according to theoretical DTFT.

clc; clear all; close all;

% Analog signal of duration Td, sampled at Fs Hz, producing L samples:
Td = 0.1;               % observaton interval in seconds
f0 = 50;                % input sine frequency in Hz.
Fs = 500;               % sampling frequency in Hz.
t = 0:1/Fs:Td;          % sampling instants time
L = length(t);          % length of actually sampled signal

A = 1.5;                % amplitude of the analog sine wave
x = A * sin(2*pi*f0*t); % samples of the analog signal...

M = 2*L;                % M: zero padding (on those L samples of x[n])
N = L + M;              % N: final DFT / FFT length, which is zero padded to N samples
xw = [x , zeros(1,M)];  % zero padded signal generated explicitly

% Compute (inefficently) N-point DFT without (1/N) weighting
sum = 0;
Xk = zeros(1,N);
for k = 1:N
sum = 0;
for n = 1:N
sum = sum + xw(n) * exp(-j*2*pi*(k-1)*(n-1) / N) ;
end
Xk(k) = sum;
end

% Display the results:
figure,subplot(3,1,1)
stem(xw);
title(['L = ', num2str(L), ' point sequence x[n], padded with M = ',...
num2str(M), ' samples into length of N = ',num2str(N),' samples']);
subplot(3,1,2)
stem(linspace(-Fs/2,Fs/2,N) , abs(fftshift(fft( xw , N ))) / (L/2) );
title('Magnitude, N-point DFT (matlab) of zero padded xw[n]');
subplot(3,1,3)
stem(linspace(-Fs/2,Fs/2,N) , abs(fftshift(Xk) / (L/2) )  );
xlabel('Magnitude, N-point DFT (explicit) of zero padded xw[n]');