Skip to content

Commit

Permalink
#35 WIP new ObjectiveGainControl
Browse files Browse the repository at this point in the history
  • Loading branch information
sheirerd committed Apr 30, 2023
1 parent ff4b7ef commit 4147f75
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 26 deletions.
Expand Up @@ -129,7 +129,7 @@ public float max(float referenceValue)
}
}

System.out.println("Max [" + max + "] from " + Arrays.toString(mBuffer));
// System.out.println("Max [" + max + "] from " + Arrays.toString(mBuffer));

return max;
}
Expand Down
46 changes: 21 additions & 25 deletions src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java
Expand Up @@ -20,10 +20,13 @@
package io.github.dsheirer.dsp.gain;

import io.github.dsheirer.buffer.FloatCircularBuffer;
import io.github.dsheirer.source.wave.RealWaveSource;
import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;

/**
* Audio automatic gain control.
*/
Expand All @@ -32,14 +35,12 @@ public class AutomaticGainControl
private static final Logger mLog = LoggerFactory.getLogger(AutomaticGainControl.class);
private static final float SAMPLE_RATE = 8000;
/* Signal delay line - time delay in seconds */
private static final float DELAY_TIME_CONSTANT = 0.015f;
private static final float DELAY_TIME_CONSTANT = 0.015f;
/* Peak detector window - time delay in seconds */
private static final float WINDOW_TIME_CONSTANT = 0.018f;
/* Attack time constants in seconds */
private static final float ATTACK_RISE_TIME_CONSTANT = 0.002f;
private static final float ATTACK_FALL_TIME_CONSTANT = 0.005f;
// private static final float ATTACK_RISE_TIME_CONSTANT = 0.2f;
// private static final float ATTACK_FALL_TIME_CONSTANT = 0.5f;
private static final float ATTACK_RISE_ALPHA = 1.0f - (float)FastMath.exp(-1.0 / SAMPLE_RATE * ATTACK_RISE_TIME_CONSTANT);
private static final float ATTACK_FALL_ALPHA = 1.0f - (float)FastMath.exp(-1.0 / SAMPLE_RATE * ATTACK_FALL_TIME_CONSTANT);
/* AGC decay value in milliseconds (20 to 5000) */
Expand All @@ -48,26 +49,24 @@ public class AutomaticGainControl
private static final float DECAY_RISEFALL_RATIO = 0.3f;
private static final float DECAY_RISE_ALPHA = 1.0f - (float)FastMath.exp(-1.0 / (SAMPLE_RATE * DECAY * .001 * DECAY_RISEFALL_RATIO));
private static final float DECAY_FALL_ALPHA = 1.f - (float)FastMath.exp(-1.0 / (SAMPLE_RATE * DECAY * .001));
/* Hang timer release decay time constant in seconds */
private static final float RELEASE_TIME_CONSTANT = 0.05f;
/* Specifies the AGC Knee in dB if AGC is active (nominal range -160 to 0 dB) */
private static final float THRESHOLD = -20.0f;
/* Limit output to about 3db of maximum */
private static final float AGC_OUT_SCALE = 0.7f;
/* Keep max input and output the same */
private static final float MAX_AMPLITUDE = 32767.0f; //1.0;
private static final float MAX_MANUAL_AMPLITUDE = 32767.0f; //1.0;
private static final float MAX_AMPLITUDE = 1.0f; //1.0;
private static final float MAX_MANUAL_AMPLITUDE = 1.0f; //1.0;
/* Specifies AGC manual gain in dB if AGC is not active ( 0 to 100 dB) */
private static final float MANUAL_GAIN = -90.308735f;
private static final float MANUAL_GAIN = 0.0f;
private static final float MANUAL_AGC_GAIN = MAX_MANUAL_AMPLITUDE * (float)FastMath.pow(10.0, MANUAL_GAIN / 20.0);
/* Limit output to about 3db of maximum */
private static final float AGC_OUT_SCALE = 0.95f; //0.7f;
/* Specifies dB reduction in output at knee from max output level (0 - 10dB) */
private static final float SLOPE_FACTOR = 2.0f;
private static final float KNEE = THRESHOLD / 20.0f;
/* Specifies the threshold when the AGC kicks in (nominal range -160 to 0 dB) */
private static final float THRESHOLD = -20.0f; //-20.0f
private static final float KNEE = THRESHOLD / 20.0f; //20.0f
private static final float GAIN_SLOPE = SLOPE_FACTOR / 100.0f;
private static final float FIXED_GAIN = AGC_OUT_SCALE * (float)FastMath.pow(10.0, KNEE * (GAIN_SLOPE - 1.0));
/* Constant for calc log() so that a value of 0 magnitude = -8 */
private static final float MIN_CONSTANT = 3.2767E-4f;
private boolean mAGCEnabled = false;
private boolean mAGCEnabled = true;
private float mPeakMagnitude = -5.0f;
private float mAttackAverage = 0.0f;
private float mDecayAverage = 0.0f;
Expand All @@ -81,16 +80,17 @@ public class AutomaticGainControl
*/
public AutomaticGainControl()
{
System.out.println("Fixed gain: " + FIXED_GAIN + " Knee: " + KNEE + " Gain Slope:" + GAIN_SLOPE);
System.out.println("Manual gain: " + MANUAL_AGC_GAIN);
}

public void reset()
{
mLog.info("Previous - Min Gain: " + mMinGain + " Max Gain: " + mMaxGain);
mLog.info("Resetting - Peak:" + mPeakMagnitude + " Attack:" + mAttackAverage + " Decay:" + mDecayAverage + " Mag Max:" + mMagnitudeBuffer.max(mPeakMagnitude));
mPeakMagnitude = -5.0f;
mAttackAverage = -5.0f;
mDecayAverage = -5.0f;
mPeakMagnitude = -8.0f;
mAttackAverage = -8.0f;
mDecayAverage = -8.0f;
mDelayBuffer.reset(0.0f);
mMagnitudeBuffer.reset(mPeakMagnitude);
}
Expand Down Expand Up @@ -139,7 +139,7 @@ public float process(float currentSample)
else if(delayedMagnitude == mPeakMagnitude)
{
/* If delayed magnitude is the current peak, then find a new peak */
mPeakMagnitude = mMagnitudeBuffer.max(-5.0f);
mPeakMagnitude = mMagnitudeBuffer.max(-3.4845634f);
}

/* Exponential decay mode */
Expand Down Expand Up @@ -173,7 +173,9 @@ else if(delayedMagnitude == mPeakMagnitude)
gain = AGC_OUT_SCALE * (float)FastMath.pow(10.0, magnitude * (GAIN_SLOPE - 1.0));
}

// mLog.info("Peak: " + mPeakMagnitude + " Attack: " + mAttackAverage + " Decay: " + mDecayAverage + " Gain: " + gain);
System.out.println("Current Mag: " + currentMagnitude + " Delay Mag: " + delayedMagnitude +
" Peak:" + mPeakMagnitude + " Gain:" + gain +
" Attack Avg:" + mAttackAverage + " Decay Avg:" + mDecayAverage);
}

if(gain < mMinGain)
Expand Down Expand Up @@ -206,10 +208,4 @@ public boolean isAGCEnabled()
return mAGCEnabled;
}

public static void main(String[] args)
{
AutomaticGainControl agc = new AutomaticGainControl();

System.out.println("Finished.");
}
}
@@ -0,0 +1,94 @@
package io.github.dsheirer.dsp.gain;

import org.apache.commons.math3.util.FastMath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ObjectiveGainControl
{
private static final Logger mLog = LoggerFactory.getLogger(ObjectiveGainControl.class);
private float mMinGain;
private float mMaxGain;
private float mCurrentGain = 1.0f;
private float mObjectiveGain = mCurrentGain;
private float mObjectiveAmplitude;
private float mMaxObservedAmplitude;

public ObjectiveGainControl(float minGain, float maxGain, float objectiveAmplitude)
{
mMinGain = minGain;
mMaxGain = maxGain;
mObjectiveAmplitude = objectiveAmplitude;
}

public void reset()
{
mMaxObservedAmplitude = 0.0f;
}

public float[] process(float[] samples)
{
float currentAmplitude;

for(float sample: samples)
{
currentAmplitude = FastMath.abs(sample);

if(currentAmplitude > mMaxObservedAmplitude)
{
mMaxObservedAmplitude = currentAmplitude;
}
}

mObjectiveGain = mObjectiveAmplitude / mMaxObservedAmplitude;

if(mObjectiveGain > mMaxGain)
{
mObjectiveGain = mMaxGain;
}
else if(mObjectiveGain < mMinGain)
{
mObjectiveGain = mMinGain;
}

if(mCurrentGain != mObjectiveGain)
{
mLog.info("Current: " + mCurrentGain + " Objective: " + mObjectiveGain);
float incrementalGainChange = mObjectiveGain / samples.length / 4; //Aim to achieve objective over 4x sample buffers
float gain = mCurrentGain;

float[] processed = new float[samples.length];
for(int x = 0; x < samples.length; x++)
{
gain += incrementalGainChange;

if(gain > mMaxGain)
{
gain = mMaxGain;
}
if(gain < mMinGain)
{
gain = mMinGain;
}

processed[x] = samples[x] * gain;
}

if(Math.abs(mObjectiveGain - mCurrentGain) < incrementalGainChange)
{
mLog.info("Objective reached!");
mCurrentGain = mObjectiveGain;
}
else
{
mCurrentGain = gain;
}

return processed;
}
else
{
return samples;
}
}
}
30 changes: 30 additions & 0 deletions src/main/java/io/github/dsheirer/module/decode/am/AMDecoder.java
Expand Up @@ -21,8 +21,12 @@

import io.github.dsheirer.dsp.am.SquelchingAMDemodulator;
import io.github.dsheirer.dsp.gain.AutomaticGainControl;
import io.github.dsheirer.dsp.gain.ObjectiveGainControl;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.analog.SquelchingAnalogDecoder;
import io.github.dsheirer.source.wave.RealWaveSource;

import java.io.File;

/**
* Decoder module with integrated squelching AM demodulator
Expand Down Expand Up @@ -69,4 +73,30 @@ protected void notifyCallStart()
mAGC.reset();
super.notifyCallStart();
}

public static void main(String[] args)
{
ObjectiveGainControl agc = new ObjectiveGainControl(1.0f, 4.0f, 0.7f);

String path = "C:\\Users\\sheirerd\\SDRTrunk\\recordings\\20230430_052528Air_Boston_Center__TO_3.wav";
File file = new File(path);

try
{
RealWaveSource source = new RealWaveSource(file);
source.setListener(floats -> agc.process(floats));
source.open();

while(true)
{
source.next(256, true);
}

}
catch(Exception e)
{
// mLog.error("Error", e);
}
System.out.println("Finished.");
}
}

0 comments on commit 4147f75

Please sign in to comment.