[hpsdr] frequency-specific frequency domain AGC

Roger Rehr W3SZ w3sz73 at gmail.com
Thu Jul 13 06:58:50 PDT 2017


Hi All,

This email is about a software/DSP enhancement that those of you writing
software for the HPSDR (or other hardware) may want to consider.

The explosion in popularity of the WSJT modes since the first of the
year has presented me (and probably many of you as well) with a new
problem, due to the now frequent occurrence of very strong local
stations on the VHF digital frequencies where I am trying to work very
weak, distant stations.  This was not previously an issue for me, as
there were not other locals active on these frequencies.  But these
frequencies are now like Grand Central Station in NYC. 

I do NOT get RF overload from these strong local stations, that is well
taken care of due to appropriate station design.  But the computer final
audio stages cannot tolerate the wide range of signal levels present
without some sort of AGC.

And as everyone on this list knows, one does NOT want to run AGC for
weak signal work digital or otherwise, because the action of the AGC on
a strong station within the AGC passband will mask the weak stations.

This problem was particularly vexsome on Monday of this week when a very
strong local station seemed to be omnipresent.  I had 2 states of
operation [1] AGC off and [2] AGC on.  With [1], the strong station
would overload the audio when the audio levels were adjusted for proper
copy of the weak DX stations.  With [2], when the strong station came on
the weak stations' signal levels were pushed way down so that they were
no longer copiable/visible.

The problem with the usual AGC is that it is done in the time domain,
and it is not frequency specific.  And to solve this interference
problem we need frequency-specific AGC.

But in the SDR receive chain we are in the frequency domain for audio
filtering anyway, and so there is little computing penalty for adding
some simple code that applies frequency-specific AGC to each individual
FFT bin.

It was a simple matter to write a few lines of code to add the
frequency-specific AGC code to run immediately after the filter code,
and the results thus far have been outstanding here with both JT65 and
FT8 weak signal work in the presence of strong stations. It is only
Thursday and I started playing with this only Tuesday, so more real
world testing is to be done.

But the results of this have been so outstanding for me that I wanted to
share them with the rest of the group so that they can add this function
to their software too.  My code is idiosyncratic to my software here,
but the basic principles are general.  Here, the frequency-specific AGC
code working in the frequency domain by limiting the signal strengths
individually for each FFT bin is placed just after the filter
deconvolution is done, and before the "usual" time-domain AGC, which I
have disabled. 

The approach I am using is to allow the user (me) to select a "knee"
amplitude and a "limit" amplitude.  Signals below the knee amplitude
are  passed without change. Signals above the knee amplitude are
modified using these equations:

++++++++++++++++
 thisMagnitude[i] = (float)Math.Sqrt(w3sz_tmp_cpx[i].real *
w3sz_tmp_cpx[i].real + w3sz_tmp_cpx[i].imaginary *
w3sz_tmp_cpx[i].imaginary)
--------------------------
   float w =( -(float)Math.Log(1 - (1 / this.fagclimit))) /
this.fagcknee;  
           
            for (int i = 0; i < size; i++)
            {
                if (this.fagctype == "OFF")  //turns of time-domain AGC
                {
                    if (thisMagnitude[i] > this.fagcknee)  // frequency
domain formula for amplitude > knee amplitude
                    {
                        d.tmp_cpx_2[i].real = 0.0001f * this.fagclimit *
(1.0f - (float)Math.Exp(-w * thisMagnitude[i])) * w3sz_tmp_cpx[i].real;
                        d.tmp_cpx_2[i].imaginary = (d.tmp_cpx_2[i].real
/ w3sz_tmp_cpx[i].real) * w3sz_tmp_cpx[i].imaginary;
                    }
                    else  // frequency domain formula for amplitude <=
knee amplitude
                    {
                        d.tmp_cpx_2[i].real = 0.0001f *
w3sz_tmp_cpx[i].real;
                        d.tmp_cpx_2[i].imaginary = 0.0001f *
w3sz_tmp_cpx[i].imaginary;
                    }
                }
                else  //if time domain AGC is to be used instead
(separate code block), there is some level setting done here
                {
                    d.tmp_cpx_2[i].real = 0.01f * w3sz_tmp_cpx[i].real;
                    d.tmp_cpx_2[i].imaginary = 0.01f *
w3sz_tmp_cpx[i].imaginary;
                }
            }
+++++++++++++++
where:
w3sz_tmp_cpx is input
d.tmp.cpx_2 is output

w sets the level of the signal at the knee calculated by the the
formulas above to be  equal to the input signal, so that there is no
discontinuity at the knee.
this.fagclimit is the limit amplitude
this.fagcknee is the knee amplitude

The most useful range for user selectable signal limits here thus far is
1-20 and useful range for knees is 0.0001 to 0.02.

the initial factors of 0.0001 and 0.01 in the above formulas are just to
set baseline signal level as appropriate for my setup here.

This approach should be easy for anyone to adapt to their software, and
I've been very pleased with the results here.
And of course NONE of this would have happened without the work of Phil
VK6PH  and Kiss Konsole.

If you want more details on my software or want to download my entire
codebase, you can go to
http://www.nitehawk.com/w3sz/CSharpsdrclientANDserverVersion2pt0.html

73,

Roger Rehr
W3SZ

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.openhpsdr.org/pipermail/hpsdr-openhpsdr.org/attachments/20170713/f4da5951/attachment.htm>


More information about the Hpsdr mailing list