The error in spectrum amplitudes of the 3 tones is caused by low resolution caused by the way fft is applied, and if the input signal is undersampled, which is not the case.
To show that there are fft parameters that increase amplitude and resolution, therefore achieving amplitude readings close to expected values, I have written for you the following MATLAB script.
If you find this answer useful would you please consider clicking on the accepted answer.
clear all;close all;clc % clear the table
1.- Reproducing signal xn(t)
I rewrite your 3-tone signal with compact notation
fs=16000; % [Hz] sampling frequency
dt=1/fs
at=2048 % amount time samples
nt=[0:at-1]; % time indexes
t=round(nt*dt,4);
f0=45; % [Hz] base pitch
f1=f0*[1:3]; % all tone frequencies
ph1=round(1.9pi,4) % phase shifts, just 4 decimals
ph2=round(2.9pi,4)
ph3=round(.3*pi,4)
ph=[ph1 ph2 ph3];
a=2pif1'*t+ph'; % constant phases
s_ind=sin(a); % individual signals A=1
A=[170 220 150] % amplitudes
% combined signal
xn=sum(A'.sin(a),1);
% same as
% xn=A(1)s1+A(2)s2+A(3)s3;
2.- Reproducing Xn(f)=fft(xn)
Xn=fft(xn);
absXn = abs(Xn);
% frequency window, as in question
nw_fft=50
figure(1)
ax1=gca
plot(ax1,absXn([1:nw_fft]))
grid on
xlabel('f')
title(ax1,['FFT(|Xn(f)|) fs = ' num2str(fs) 'Hz, amount t samples = ' num2str(at)])

3.- Align frequency
when fft not supplied with amount frequencies then fft amount frequencies = amount time samples af : amount frequencies, default, or so it tries.
af=at
af=length(Xn)
f = fs*[0:af-1]/at;
4.- Remove repeated spectrum
real time signals (no quadrature) produce fft with repeated samples, Above fs/2 there on Xn=fft(xn) samples are mirrored, so let's remove them
Xn([floor(af/2):end])=[];
absXn([floor(af/2):end])=[];
f([floor(af/2):end])=[];
5.- Xn(f) amplitude correction
FFT amplitudes should be independent of amount input time samples
Xn=2*Xn/at; % *2 to preserve signal energy
absXn=2*absXn/at;
figure(2)
ax2=gca
plot(ax2,f(1:nw_fft),absXn(1:nw_fft))
% plot(ax2,f,absXn)
hold(ax2,'on')
grid on
xlabel('f')
title(ax2,['FFT(|Xn(f)|) fs = ' num2str(fs) ' amount t samples = ' num2str(at)])
detected frequencies and amplitudes
[pks,locs]=findpeaks(absXn(1:nw_fft),'NPeaks',3)
fpeaks=(locs-1)*fs/at % detected frequency peaks
plot(ax2,fpeaks,pks,'ro')
err_f=abs(fpeaks-f1)./(f1)100 % frequency error
err_absXn=abs(abs(pks)-abs(A))./abs(A)100 % amplitude error
str1=['f errors f1: ' ...
num2str(round(err_f(1),2)) '% f2: ' ...
num2str(round(err_f(2),2)) '% f3: ' ...
num2str(round(err_f(3),2)) '%.']
text(ax2,max(xlim)/2,max(ylim)/2,str1)
str2=['A errors A1: ' ...
num2str(round(err_absXn(1),2)) '% A2: ' ...
num2str(round(err_absXn(2),2)) '% A3: ' ...
num2str(round(err_absXn(3),2)) '%.']
text(ax2,max(xlim)/2,max(ylim)/2-10,str2)

6.- Improving frequency and amplitude resolution
% what is the current frequency resolution
f(1)
f(2)
% same as
fs/at
f(2) > 1Hz. As pointed out in one of the posted comments the frequency resolution is too low to resolve these 3 tones with error below 1Hz.
If there are frequency errors, there are also amplitude errors.
Problem : Sampling really fast but not supplying enough time samples to the fft reduces frequency resolution.
Because fs=16000Hz is well above 2*3*f1 = 270Hz there may be overhead to play with.
Solution : Sampling slower, while still above Nyquist limit, may reduce fft frequency steps and therefore incrase frequency resolution.
Nyquist limit : 2 samples per cylce of signal highest frequency is the limit to avoid degrading signal by undersampling:
fs_nyquist=2*max(signal_f)
One must sample above this limit.
Every signal has it's own Nyquist limit, to avoid sampling below it or start losing signal
f_nyquist=3*2*f1(end)
% let's use 16 sample per cycle of signal highest frequency
fs2=8*f_nyquist % now fs=2160
dt2=1/fs2
t2=nt*dt2;
a2=2pif1't2+ph';
xn2=sum(A'.sin(a2),1);
fft
Xn2=fft(xn2);
absXn2 = abs(Xn2);
new frequency reference
af2=length(Xn2)
f2 = fs2*[0:af2-1]/at;
fft length and amplitude corrections
Xn2([floor(af2/2):end])=[];
absXn2([floor(af2/2):end])=[];
f2([floor(af2/2):end])=[];
Xn2=2Xn2/at; % Xn amplitude corrections
absXn2=2absXn2/at;
figure(3)
ax3=gca
plot(ax3,f2([1:3*nw_fft]),absXn2([1:3*nw_fft]))
hold(ax3,'on')
grid on
xlabel('f')
title(ax3,['FFT(|Xn(f)|) fs = ' num2str(fs2) 'Hz amount t samples = ' num2str(at)])
detected frequencies and amplitudes
[pks2,locs2]=findpeaks(absXn2([1:3*nw_fft]),'NPeaks',3) % ,'SortStr','ascend');
fpeaks2=(locs2-1)*fs2/at % detected frequency peaks
plot(ax3,fpeaks2,pks2,'ro')
err_f2=abs(fpeaks2-f1)./(f1)100 % frequency error
err_absXn2=abs(abs(pks2)-abs(A))./abs(A)100 % amplitude error
str1=['f1:' ...
num2str(round(fpeaks2(1),2)) 'Hz f2:' ...
num2str(round(fpeaks2(2),2)) 'Hz f3:' ...
num2str(round(fpeaks2(3),2)) 'Hz']
text(ax3,max(xlim)/2,max(ylim)/2+20,str1)
str2=['A1:' ...
num2str(round(pks2(1),2)) ' A2:' ...
num2str(round(pks2(2),2)) ' A3:' ...
num2str(round(pks2(3),2))]
text(ax3,max(xlim)/2,max(ylim)/2+10,str2)
str3=['f errors f1:' ...
num2str(round(err_f2(1),2)) '% f2:' ...
num2str(round(err_f2(2),2)) '% f3:' ...
num2str(round(err_f2(3),2)) '%']
text(ax3,max(xlim)/2,max(ylim)/2,str3)
str4=['A errors A1:' ...
num2str(round(err_absXn2(1),2)) '% A2:' ...
num2str(round(err_absXn2(2),2)) '% A3:' ...
num2str(round(err_absXn2(3),2)) '%']
text(ax3,max(xlim)/2,max(ylim)/2-10,str4)

now frequency error below 1Hz for all carriers yet amplitude errors are still significant
7.1.- Sampling faster while keeping same amount time samples
fs2=2*fs % 10*f_nyquist % now fs=2160
% at=12048 % amount time samples
% nt=[0:at-1]; % time indexes
dt2=1/fs2
t2=nt*dt2;
a2=2pif1't2+ph';
xn2=sum(A'.sin(a2),1);
% fft
Xn2=fft(xn2);
absXn2 = abs(Xn2);
% new frequency reference
af2=length(Xn2)
f2 = fs2*[0:af2-1]/at;
% fft length and amplitude corrections
Xn2([floor(af2/2):end])=[];
absXn2([floor(af2/2):end])=[];
f2([floor(af2/2):end])=[];
Xn2=2Xn2/at; % Xn amplitude corrections
absXn2=2absXn2/at;
figure(4)
ax4=gca
plot(ax4,f2([1:3*nw_fft]),absXn2([1:3*nw_fft]))
hold(ax4,'on')
grid on
xlabel('f')
title(ax4,['FFT(|Xn(f)|) fs = ' num2str(fs2) 'Hz amount t samples = ' num2str(at)])
% detected frequencies and amplitudes
[pks2,locs2]=findpeaks(absXn2,'NPeaks',3)
fpeaks2=(locs2-1)*fs2/at % detected frequency peaks
plot(ax4,fpeaks2,pks2,'ro')
err_f2=abs(fpeaks2-f1)./(f1)100 % frequency error
err_absXn2=abs(abs(pks2)-abs(A))./abs(A)100 % amplitude error
str1=['f1:' ...
num2str(round(fpeaks2(1),2)) 'Hz f2:' ...
num2str(round(fpeaks2(2),2)) 'Hz f3:' ...
num2str(round(fpeaks2(3),2)) 'Hz']
text(ax4,max(xlim)/2,max(ylim)/2+20,str1)
str2=['A1:' ...
num2str(round(pks2(1),2)) ' A2:' ...
num2str(round(pks2(2),2)) ' A3:' ...
num2str(round(pks2(3),2))]
text(ax4,max(xlim)/2,max(ylim)/2+10,str2)
str3=['f errors f1:' ...
num2str(round(err_f2(1),2)) '% f2:' ...
num2str(round(err_f2(2),2)) '% f3:' ...
num2str(round(err_f2(3),2)) '%']
text(ax4,max(xlim)/2,max(ylim)/2,str3)
str4=['A errors A1:' ...
num2str(round(err_absXn2(1),2)) '% A2:' ...
num2str(round(err_absXn2(2),2)) '% A3:' ...
num2str(round(err_absXn2(3),2)) '%']
text(ax4,max(xlim)/2,max(ylim)/2-10,str4)

7.2.- Sampling faster, more time samples = more frequencies
fs2=10*fs
at=12048 % amount time samples
nt=[0:at-1]; % time indexes
dt2=1/fs2
t2=nt*dt2;
a2=2pif1't2+ph';
xn2=sum(A'.sin(a2),1);
% fft
Xn2=fft(xn2,at);
absXn2 = abs(Xn2);
% new frequency reference
af2=length(Xn2)
% f2 = fs2[0:af2-1]/at;
f2 = fs2[0:af2-1]/af2;
% fft length and amplitude corrections
Xn2([floor(af2/2):end])=[];
absXn2([floor(af2/2):end])=[];
f2([floor(af2/2):end])=[];
Xn2=2Xn2/at; % Xn amplitude corrections
absXn2=2absXn2/at;
figure(5)
ax5=gca
plot(ax5,f2([1:10*nw_fft]),absXn2([1:10*nw_fft]))
hold(ax5,'on')
grid on
xlabel('f')
title(ax5,['FFT(|Xn(f)|) fs = ' num2str(fs2) 'Hz amount t samples = ' num2str(at)])
% detected frequencies and amplitudes
[pks2,locs2]=findpeaks(absXn2, 'NPeaks',3,'MinPeakHeight',10)
fpeaks2=fs2*(locs2-1)/af2 % detected frequency peaks
plot(ax5,fpeaks2,pks2,'ro')
err_f2=abs(fpeaks2-f1)./(f1)100 % frequency error
err_absXn2=abs(abs(pks2)-abs(A))./abs(A)100 % amplitude error
str1=['f1:' ...
num2str(round(fpeaks2(1),2)) 'Hz f2:' ...
num2str(round(fpeaks2(2),2)) 'Hz f3:' ...
num2str(round(fpeaks2(3),2)) 'Hz']
text(ax5,max(xlim)/2,max(ylim)/2+20,str1)
str2=['A1:' ...
num2str(round(pks2(1),2)) ' A2:' ...
num2str(round(pks2(2),2)) ' A3:' ...
num2str(round(pks2(3),2))]
text(ax5,max(xlim)/2,max(ylim)/2+10,str2)
str3=['f errors f1:' ...
num2str(round(err_f2(1),2)) '% f2:' ...
num2str(round(err_f2(2),2)) '% f3:' ...
num2str(round(err_f2(3),2)) '%']
text(ax5,max(xlim)/2,max(ylim)/2,str3)
str4=['A errors A1:' ...
num2str(round(err_absXn2(1),2)) '% A2:' ...
num2str(round(err_absXn2(2),2)) '% A3:' ...
num2str(round(err_absXn2(3),2)) '%']
text(ax5,max(xlim)/2,max(ylim)/2-10,str4)


Now it looks as if there were again loss frequency/amplitude resolution but actually checking the errors one realises that the sketchy spectrum is accurate, because now both frequency and amplitude resolutions have enough small steps to measure accurately.
8.- Amplitude and frequency errors as function of sampling fs and at
at_range=2.^[6:.1:20];
fs_range=linspace(90,64000,280);
err_f_log=zeros(numel(fs_range),numel(at_range),3);
err_A_log=zeros(numel(fs_range),numel(at_range),3);
for k1=1:1:numel(fs_range)
for k2=1:1:numel(at_range)
dt2=1/fs_range(k1);
nt=[0:at_range(k2)-1]; % time indexes
t2=nt*dt2;
% sin() inputs
a1=2pif1(1)t2+ph1;a2=2pif1(2)t2+ph2;a3=2pif1(3)*t2+ph3;
% individual signals A=1
s1=sin(a1);s2=sin(a2);s3=sin(a3);
% combined signal
xn=A(1)s1+A(2)s2+A(3)*s3;
% fft
Xn=fft(xn);Xn2=2*Xn2/at; % Xn amplitude corrections
af=length(Xn);
f = fs_range(k1)*[0:af-1]/at_range(k2);
Xn([floor(af/2):end])=[];f([floor(af/2):end])=[];
absXn=abs(Xn);
% detected frequencies and amplitudes
[pks,locs]=findpeaks(absXn,'NPeaks',3,'MinPeakHeight',10);
fpeaks=(locs-1)*fs_range(k1)/at_range(k2); % detected frequency peaks
% plot(ax4,fpeaks2,pks2,'ro') % same as findpeaks option 'Annotate',['peaks' | 'extents']
switch numel(pks)
case 0
err_f=[NaN NaN NaN];
err_absXn=[NaN NaN NaN];
case 1 % assume single f detected then it's f0, although could be alias
err_f(1)=abs(fpeaks-f1(1))./(f1(1))*100;
err_f(2)=NaN;
err_f(3)=NaN;
err_absXn(1)=abs(abs(pks)-abs(A(1)))./abs(A(1))*100;
err_absXn(2)=NaN;
err_absXn(3)=NaN;
case 2
err_f(1)=abs(fpeaks(1)-f1(1))./(f1(1))*100;
err_f(2)=abs(fpeaks(2)-f1(2))./(f1(2))*100;
err_f(3)=NaN;
err_absXn(1)=abs(abs(pks(1))-abs(A(1)))./abs(A(1))*100;
err_absXn(2)=abs(abs(pks(2))-abs(A(2)))./abs(A(2))*100;
err_absXn(3)=NaN;
case 3
err_f=abs(fpeaks-f1)./(f1)*100;
err_absXn=abs(abs(pks)-abs(A))./abs(A)*100;
otherwise
disp('findpeaks should return up to 3 peaks only')
end
% update logs
err_f_log(k1,k2,1)=err_f(1);
err_f_log(k1,k2,2)=err_f(2);
err_f_log(k1,k2,3)=err_f(3);
err_A_log(k1,k2,1)=err_absXn(1);
err_A_log(k1,k2,2)=err_absXn(2);
err_A_log(k1,k2,3)=err_absXn(3);
end
end
plot error surfaces as function of fs and at look for lowest point
% frequency error
figure(6)
ax6=gca
hs6=surf(err_f_log(:,:,1));
hs6.EdgeColor='none'
xlabel('fs [Hz]');ylabel('amount time samples');zlabel('f error [%]')
title(ax6,['f1 = ' num2str(f1(1)) 'Hz error [%]'])

figure(7)
ax7=gca
hs7=surf(err_f_log(:,:,2));
hs7.EdgeColor='none'
xlabel('fs [Hz]');ylabel('amount time samples');zlabel('f error [%]')
title(ax7,['f1 = ' num2str(f1(2)) 'Hz error [%]'])

figure(8)
ax8=gca
hs8=surf(err_f_log(:,:,3));
hs8.EdgeColor='none'
xlabel('fs [Hz]');ylabel('amount time samples');zlabel('f error [%]')
title(ax8,['f1 = ' num2str(f1(3)) 'Hz error [%]'])

amplitude error
figure(9)
ax9=gca
hs9=surf(err_A_log(:,:,1));
hs9.EdgeColor='none'
xlabel('fs [Hz]');ylabel('amount time samples');zlabel('A error [%]')
title(ax9,['A = ' num2str(A(1)) ' error [%]'])

figure(10)
ax10=gca
hs10=surf(err_A_log(:,:,2));
hs10.EdgeColor='none'
xlabel('fs [Hz]');ylabel('amount time samples');zlabel('A error [%]')
title(ax10,['A = ' num2str(A(2)) ' error [%]'])

figure(11)
ax11=gca
hs11=surf(err_f_log(:,:,3));
hs11.EdgeColor='none'
xlabel('fs [Hz]');ylabel('amount time samples');zlabel('A error [%]')
title(ax11,['A = ' num2str(A(3)) ' error [%]'])

9.1.- af up holding at constant changing frequency correction
nf=2.^[10:15]; % amount fft frequencies
figure
ax=gca
for k=1:1:numel(nf)
Xn=fft(xn,nf(k));
absXn=abs(Xn);
f = fs*[0:nf(k)-1]/at;
absXn([floor(nf(k)/2):end])=[];
f([floor(nf(k)/2):end])=[];
absXn=2*absXn/at;
plot(ax,f,absXn)
hold(ax,'on')
grid on
xlabel('f')
end
title(['|Xn(f)| with nf ' num2str(nf)]);

In this graph I did not update the frequency axis because it would get too crowded.
Make the amount of fft frequencies a power of 2, otherwise again loss of resolution happens shown in next point.
9.2.- af up holding at constant with more frequencies
changing frequency correction
nf=floor(linspace(2048,8192,10)); % amount fft frequencies
figure
ax=gca
for k=1:1:numel(nf)
Xn=fft(xn,nf(k));
absXn=abs(Xn);
f = fs*[0:nf(k)-1]/at;
absXn([floor(nf(k)/2):end])=[];
f([floor(nf(k)/2):end])=[];
absXn=2*absXn/at;
plot(ax,f,absXn)
hold(ax,'on')
grid on
xlabel('f')
end
title(['|Xn(f)| with nf ' num2str(nf)]);

Just pushing more frequencies with larger nf in fft(xn,nf) keeping constant the amount of time samples deteriorates frequency resolution substantially, increasing side lobes.
nf same as at.
frequencies span and the frequency peaks with it because amount time samples at is kept constant.
If nf (nf same as at) is increased, so should amount time samples at
10.- Listening xn
fs=16000; % [Hz] sampling frequency
dt=1/fs
at=2048 % amount time samples
nt=[0:at-1]; % time indexes
t=round(nt*dt,4);
f0=45; % [Hz] base pitch
f1=f0*[1:3]; % all tone frequencies
ph1=round(1.9pi,4) % phase shifts, just 4 decimals
ph2=round(2.9pi,4)
ph3=round(.3*pi,4)
ph=[ph1 ph2 ph3];
% sin phases
a=2pif1'*t+ph';
% combined signal
xn=sum(A'.*sin(a),1);
% load example
xData=load('chirp.mat')
xObj=audioplayer(xData.y,xData.Fs)
play(xObj)
% replace data
xdata.y=xn
xData.Fs=fs
xObj.SampleRate=fs
xObj.TimerPeriod=t(end)
play(xObj)
the signal is the simluation of a bird tweet :)
Thanks for reading my answer.