AGC compensation

Info: this thread is based on an IRC discussion. Many of the text boxes had been edited afterwards, because I did not have the time to write everywhere simultaneously. I hope it will be clearer now what we were talking about.

All Nyquist code, except the plugins in later text boxes, must be run from the Audacity “Effect > Nyquist Prompt”.

Here is a Nyquist function that computes the RMS envelope of an audio signal and then re-applies the inverse of the enelope to the same signal:

(defun compensate (factor)
  (let* ((envelope (rms s 20))
         (counter-envelope (sum 1.0 (mult -1.0 factor envelope))))
    (mult s counter-envelope)))

(compensate 1)

The number in (compensate 1) is the amount of compensation. The function still makes the signal much lower, but alread compensates :wink:

I just realize that this can be further simplified to:

(defun compensate (factor)
  (let ((comp-env (sum 1.0 (mult -1.0 factor (rms s 20)))))
    (mult s comp-env)))

(compensate 1)

These were the ideas from the first few minutes of the discussion. Everything else evolved from this. The code still throws errors with stereo tracks, also the “Analyze” and “Compensate” steps are still not separated, instead the counter-envelope is applied to the same signal where it was generated from.

In this text box is shown how the RMS envelope, computed from a mono or stereo signal in an Audacity audio track, can be stored in the Nyquist scratch variable to survive subsequent “Nyquist Prompt” invocations.

First the background noise from the second camera is analyzed with the following code from the Audacity Nyquist Prompt:

(defun rms-envelope (snd)
  (if (arrayp s)
      (mult 0.5 (sum (rms (aref s 0) 20) (rms (aref s 1) 20)))
      (rms s 20)))

(progn (setf *scratch* (rms-envelope s)) s)

The envelope in the scratch variable survives “Nyquist Prompt” invocations, so the original track only needs to be analyzed once, the envelope from the scratch variable then can be used until a new audio track is analyzed.

Then the inverted envelope from the scratch variable is applied to the other audio track with the following code from the Audacity Nyquist Prompt:

(defun compensate (amp comp)
  (mult s (sum amp (mult comp *scratch*))))

(compensate 0.25 1)

The first number “0.25” in (compensate 0.25 1) is a constant overall amplification. Because the “compensate” function can make the signal louder, it’s probably better to use numbers less than 1, where 1 is “unity amplification” (= no volume change).

The second number “1” in (compensate 0.25 1) is the amount of volume changes caused by the envelope signal.

Both numbers can be an integer or floating-point numbers. The code works now with mono as well as with stereo audio tracks.

Next step - an “AGC Compensation” plugin

Just copy the “agc-compensation.ny” file from below into the Audacity “plug-ins” folder, then restart Audacity and a new “AGC Compensation” plugin should appear in the lower part of the Nyquist “Effect” menu.

  • First select the noise track and analyze the track with the plugin “Mode” set to “Analyze”.

  • Then select the other track and apply the plugin with “Mode” set to “Compensate”.

The “Amplification” and “Compensation” sliders in the plugin window only work in “Compensate” mode, they are ignored in “Analyze” mode. The “Analyze” envelope survives “Compensate” invocations until the next “Analyze” run.
agc-compensation.ny (534 Bytes)
Warning: This plugin is misfunctional, use the plugin two text boxes below instead.

I thought the correct way to calculate rms for stereo tracks was to take the root of the average of mean squares.

  (mult 0.5 (sum 
    (snd-avg (mult (aref s 0)(aref s 0)) step block op-average)
    (snd-avg (mult (aref s 1)(aref s 1)) step block op-average))))

or (same as)

(snd-sqrt (mult 0.5 
  (snd-avg (sum 
    (mult (aref s 1)(aref s 1))
    (mult (aref s 0)(aref s 0))) 
    step block op-average)))

rather than the average of the rms values.

@bencahill: The next plugin version:
agc-compensation.ny (543 Bytes)
In principle the same plugin as two text boxes above, but “Compensate” now uses the inverted envelope.

The “Amplify” preset of 1 may be too high, because of Audacity bugs I currently only can use integers to initialize float sliders. The only other alternative would be a preset of zero, what produces silence.

@steve: RMS calls SND-AVG, and all-in-all it just does what you had written above. But I must look-up the details anyway because we must find a way how to compute the correct intervals for Niklas under Analysis of Surface Electromyography-Signals.

With the AGC compensation we still have the problem that we must find the correct time alignment (the AGC always happens after the volume change, so a compensation curve must work backwards in time) together with finding the correct amplitudes. This is even more complicated than only find the RMS value. But the AGC problem was often asked (and there is no general solution how to compensate this), so will write down all the details here if we find a solution at all.

The discussion currently still runs in the #audacity IRC cannel.

@bencahill: A Nyquist plugin is just a plain text file that you can open and change with every text editor.

;nyquist plug-in
;version 3
;type process
;name "AGC Compensation..."
;action "AGC Compensation..."

;control mode "Mode" choice "Analyze,Compensate" 0
;control amp "Amplification" real "C" 1 0.25 2
;control comp "Compensation" real "C" 1 0.1 10

(defun rms-envelope (snd)
  (if (arrayp s)
      (mult 0.5 (sum (rms (aref s 0) 20) (rms (aref s 1) 20)))
      (rms s 20)))

(defun compensate (amp comp)
  (mult s (sum amp (mult -1.0 comp *scratch*))))

(if (= 0 mode)
    (progn (setf *scratch* (rms-envelope s)) s)
    (compensate amp comp))

The slider presets and left/right limits can be changed in the ;control lines at the beginning of the plugin text:

;control amp "Amplification" real "C" 1 0.25 2
;control comp "Compensation" real "C" 1 0.1 10

This is ;control “Text left” real “Text right”

More details are explained in the Audacity Wiki under Nyquist Plug-ins Reference > Slider Widget

Preliminary Summary

Reverting or compensating AGC (automatic gain control) that was applied by a recording device is not easy because there are many unpredictable parameters involved. In bencahill’s case there fortunately was a second tape with only the background noise, so the volume level of the noise signal could be used to generate a low-frequency envelope, that then could be inverted and used to compensate the AGC effect in the music signal. I’m still not sure if the AGC effect can be reverted if there is only one signal, where the music and the background are mixed in the same signal.

  • edgar

It would probably be useful if a little more background information could be given in this thread.
“AGC compensation” is commonly found in voice recorders, camcorders and such like as an attempt to automatically set the input gain for audio recording. Most commonly this is implemented as a compressor with slow attack/release parameters (similar to the Audacity compressor), but really this should be implemented in hardware in the input stage of the microphone pre-amp.

The effect/process being described in this topic is significantly different from that common case, in that it is being implemented in software post recording, and it is has extremely short attack/release times. The described code behaves more like a gate which has an inverted rms signal fed into the side chain.

The average of two rms signals is not the same as the root of averaging the squares of all samples, but I don’t want to break the flow of this topic so I’ll start a new topic here: Stereo tracks in Nyquist

Yes, after four or five hours of plugin writing I was just simply tired, so here I will try to tell the whole story.

Quote from the first text box in this thread:

bencahill had tapes of two video cameras. One camera had recorded him very close while he was playing piano, the other camera was some distance further away and recorded the same scence from the audience perspective.

In the audio track of the closer camera there was mainly the piano sound (without the audience noises) but unfortunately with AGC switched on, while in the audio track of the distant camera there was the overall sound of the room (piano sound plus audience noises) with AGC switched off.

bencahill’s original idea was to compute the envelope of the non-AGC sound and produce a counter-envelope, that then can be used to compensate the AGC effect in the piano sound of the closer camera.

The RMS envelope was computed by:

(defun rms-envelope (snd)
  (if (arrayp s)
      (mult 0.5 (sum (rms (aref s 0) 20) (rms (aref s 1) 20)))
      (rms s 20)))

The stereo RMS signal is computed mathematically incorrect (see Stereo tracks in Nyquist) but this was a minor problem here.

The counter-envelope then was computed by subtracting the RMS envelope from a constant 1.0 (unity gain) value, where (sum 1.0 (mult -1.0 x)) gives the same result as (- 1.0 x):

(sum 1.0 (mult -1.0 envelope))

The constant 1.0 unity gain then was replaced by the AMP variable (representing the value of the “Amplification” slider), and a COMP variable (representing the value of the “Compensation” slider) was used to change the intensity of the counter-envelope effect. The counter-envelope here is computed from the RMS envelope, stored in the SCRATCH variable:

(defun compensate (amp comp)
  (mult s (sum amp (mult -1.0 comp *scratch*))))

Then a plugin was written that allows to compute an RMS envelope from the audio track of the non-AGC signal of the distant camera and store the envelope signal in the SCRATCH variable. Then, in a second plugin run, the envelope from the SCRATCH variable is used to compute a counter-envelope, that can be adjusted by the “Amplification” (overall constant amplification to produce a useful output amplitude) and “Compensation” (amplitude of the compensation effect of the original RMS envelope) sliders, and finally the counter-envelope is applied to the AGC audio track of the closer camera with the piano music. This was done by:

  • First select the audio track with the non-AGC signal from the distant camera and analyze the track with the plugin “Mode” set to “Analyze”. The RMS-ENVELOPE function from above computes the RMS envelope and stores it in the SCRATCH variable.

  • Then select the audio track with the AGC signal from the closer camera and run the plugin with “Mode” set to “Compensate”. The plugin code computes a counter-envelope from the RMS envelope stored in the SCRATCH variable, adjusted by the “Amplification” and “Compensation” sliders. Then the counter-envelope is applied to the signal in the audio track.

The problem here was not the correct computation of the RMS envelope, because the audio signals were recorded at two completely different, too far apart places anyway, but the main problem was that an AGC in a recording device doesn’t react “in time”, but always is a bit “too late”, what from the Audacity perspective means that the AGC envelope, produced by the recording device, is shifted a bit to the right in the audio track. This effect could only be compensated by using the Audacity time-shift tool and shift the non-AGC sound (the sound where the RMS envelope was computed from) step-wise to the right, and then try over and over again, until the counter-envelope approximately compensated the AGC envelope in the audio track of the closer camera.

The main problem was the AGC time-shift, not the RMS envelope, where I have no idea how to compensate the time-shift automatically. Even introducing a third “Timeshift” slider would not necessarily be easier than to time-shift the non-AGC track by hand. Also a second non-AGC audio track was needed to compensate the AGC effect, where I have no idea how to keep the AGC and non-AGC signals apart if the only signal available is both signals mixed in one and the same audio track.

Feel free to ask more if something is still missing …

  • edgar

Topic split to here: