Automatic Gain Control






This is an attempt to implement some kind of gain leveling for the audio of my ssb GNU/Radio receiver. While tuning around, or when different operators with different signal strengths speak I have to 'ride' the volume control to hear the weak ones and attenuate the loud ones. The audio.sink wants floating point signal between -1 and 1, anything over that starts 'clicking' and turns into noise.

Basically I just want to follow the envelope of the audio and adjust the gain according. A quick check of google for 'dsp envelope follower' turns up this interesting lecture and down near the bottom is "Non-linear One Pole Application: The Envelope Follower", which is basically an absolute value followed by a "special one pole low-pass smoothing filter". That feedback scheme looked familiar. Playing around with different arrangements this is the best I could come up with:


                 
                
  audio -----+-----+==[sqr]----[int]----[offset]----[agc]-----out
             |                                        |
             |                                        |
             |                                        |
             +----------------------------------------+

where sqr = gr.multiply_ff()
      int = gr.iir_filter_ffd ( [.002, 0], [0, .999] )
      offset = gr.add_constant_ff(1)
      agc = gr.divide_ff()

where the signal is squared (in place of absolute value), run thru our 'leaky' integrator, with the feed forward tap adjusted to give reasonable values, add 1 then divide the input sigal with the agc signal. Using a test signal simulated by a 600hz tone mixed with a 2hz tone and a 2.2hz tone to get a low freq envelope gives this:



the red is the input signal, green the agc and blue the output, where you can see we're getting some soft cutoff over .5. Putting this code ('scale' is the previous audio output signal) into our receiver :

    sqr1 = gr.multiply_ff()
    int = gr.iir_filter_ffd ( [.002, 0], [0, .999] )
    offset = gr.add_const_ff(1)
    agc = gr.divide_ff()

    fg.connect (scale, (sqr1, 0))
    fg.connect (scale, (sqr1, 1))
    fg.connect (sqr1, int)
    fg.connect (int, offset)
    fg.connect (offset, (agc, 1))
    fg.connect (scale, (agc, 0))
    fg.connect (agc, out)
and finding a loud signal we get this real world performance:



which actually sounds much better and 'even', allowing weak signals thru and controlling the stronger ones.