I was playing with the idea of quadrature correlator earlier in the evening, and it seemed to work for some extent: Signals from "0dB" to "-50dB" worked alright, but signals "-70dB" and "-80dB" did not work any more. This can be observed when running the same function without the x = filter(b, a, x) statement produces the following results:
After a few correction it works fine, though, withput any pre-filtering
tg-15kHz-0dB-after-caps.dat : 0.000000 dB
tg-15kHz-10dB-after-caps.dat : -8.935213 dB
tg-15kHz-30dB-after-caps.dat : -29.040368 dB
tg-15kHz-50dB-after-caps.dat : -49.056581 dB
tg-15kHz-70dB-after-caps.dat : -69.483305 dB
tg-15kHz-80dB-after-caps.dat : -80.770373 dB
And it works even well with rectangular window, although a rectangular window does not suppress the spurs as well as other window functions would do. OTOH, a rectangular window has the lowest noise bandwith of all window functions. Eventually the choice of the window functions it is a trade-off between ENBW and stopband rejection. For this particular use case I tend to use Hann or Hamming. And btw, your Chebycheff filter certainly has a lager noise bandwidth than a 4k point Hann or Hamming window (-> 5...6 dB higher average noise floor), but this happens to be mitigated by the fact that RMS averaging reduces the variance of the noise floor, while vector averaging does not.
pkg load signal;
# System's MCU oscillator frequency
global XTAL = 72000000;
# System's default sampling rate
global Fs_Hz = (XTAL/6/14);
# Local oscillator frequency
global Lo_Hz = 15000;
# 0dB level
%global ref_dB = 76.260551;
global ref_dB = 108.561235;
# Analyze the signal level from the given file.
# Returns signal level in dB.
function dB = level_dB(filename)
global Fs_Hz;
global Lo_Hz;
global ref_dB;
Fn_Hz = Fs_Hz/2;
bw_Hz = 1000;
# Create a bandpass filter for the LO signal
[b, a] = cheby1(2, 0.1, [(Lo_Hz-bw_Hz/2)/Fn_Hz, (Lo_Hz+bw_Hz/2)/Fn_Hz]);
x = load(filename);
% Choose a window size which is an integral multiple of 15kHz periods
% in order to place zeros at the harmonics of 15kHz.
% (particularly useful if only a rectangular window with a low stop band rejection (high side lobes) is used)
x = x(1:4000);
NS = length(x);
ts = 1 / Fs_Hz;
t = ts * [0 : NS-1];
# Apply the bandpass filter to the signal
%x = filter(b, a, x);
# Quadrature correlator
x_re = x .* cos(2.0 * pi * Lo_Hz * t)';
x_im = x .* -sin(2.0 * pi * Lo_Hz * t)';
# Compute RMS of the detected signal
%rms_sum = 0;
%for n = 1:NS
% rms_sum += x_re(n)**2 + x_im(n)**2;
%endfor
% calculate magnitude of vector sum
rms_sum = sum(x_re) .** 2 + sum(x_im) .** 2;
rms = sqrt(rms_sum/NS);
# Convert the RMS level to dB
dB = 20*log10(rms) - ref_dB;
printf("%-30s: %f dB\n", filename, dB);
endfunction
level_dB("tg-15kHz-0dB-after-caps.dat");
level_dB("tg-15kHz-10dB-after-caps.dat");
level_dB("tg-15kHz-30dB-after-caps.dat");
level_dB("tg-15kHz-50dB-after-caps.dat");
level_dB("tg-15kHz-70dB-after-caps.dat");
level_dB("tg-15kHz-80dB-after-caps.dat");
Edit:
And I'm still complaining a bit about the fashion how you apply the IIR filter to the samples, which are way too short compared to the impulse response of the filter. The resulting truncation has the consequence that the effective frequency response of the filter (in conjunction with the RMS averaging detector) deviates significantly from the theoretical Chebycheff response. See attachment: Ideal response, and effective response at different phase. The effective result is still reasonable, but you need to be aware that - as a consequence of the truncation - your filter does no longer work as it was designed with cheby1().
Edit:
For comparison I've added the frequency responses you get from the coherent detector with rectangular (yellow), Hamming (orange) or Hanning window (blue). As you can see, the noise bandwith of all three ones is clearly narrower than your Cheby, and for Hanning and Hamming, the stop band rejection is better, too. In fact the filter truncation ruins the inherently large stop band rejection of the Chebycheff filter, so that at the end it is effectively not better than a rectangular window. The truncated Cheby still has retained its wider bandwidth, but that's not what we want either -- eventually we rather want a narrow (equivalent noise) bandwidth.