From 4147f75c3415d32a975ce82bc040619081ff3f40 Mon Sep 17 00:00:00 2001 From: sheirerd Date: Sun, 30 Apr 2023 06:31:33 -0400 Subject: [PATCH] #35 WIP new ObjectiveGainControl --- .../dsheirer/buffer/FloatCircularBuffer.java | 2 +- .../dsp/gain/AutomaticGainControl.java | 46 +++++---- .../dsp/gain/ObjectiveGainControl.java | 94 +++++++++++++++++++ .../dsheirer/module/decode/am/AMDecoder.java | 30 ++++++ 4 files changed, 146 insertions(+), 26 deletions(-) create mode 100644 src/main/java/io/github/dsheirer/dsp/gain/ObjectiveGainControl.java diff --git a/src/main/java/io/github/dsheirer/buffer/FloatCircularBuffer.java b/src/main/java/io/github/dsheirer/buffer/FloatCircularBuffer.java index 3fa6beee8..e7df429b3 100644 --- a/src/main/java/io/github/dsheirer/buffer/FloatCircularBuffer.java +++ b/src/main/java/io/github/dsheirer/buffer/FloatCircularBuffer.java @@ -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; } diff --git a/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java index f1dbbeaf3..caf917219 100644 --- a/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java +++ b/src/main/java/io/github/dsheirer/dsp/gain/AutomaticGainControl.java @@ -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. */ @@ -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) */ @@ -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; @@ -81,6 +80,7 @@ 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); } @@ -88,9 +88,9 @@ 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); } @@ -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 */ @@ -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) @@ -206,10 +208,4 @@ public boolean isAGCEnabled() return mAGCEnabled; } - public static void main(String[] args) - { - AutomaticGainControl agc = new AutomaticGainControl(); - - System.out.println("Finished."); - } } diff --git a/src/main/java/io/github/dsheirer/dsp/gain/ObjectiveGainControl.java b/src/main/java/io/github/dsheirer/dsp/gain/ObjectiveGainControl.java new file mode 100644 index 000000000..453576db2 --- /dev/null +++ b/src/main/java/io/github/dsheirer/dsp/gain/ObjectiveGainControl.java @@ -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; + } + } +} diff --git a/src/main/java/io/github/dsheirer/module/decode/am/AMDecoder.java b/src/main/java/io/github/dsheirer/module/decode/am/AMDecoder.java index 2c3be5484..ca0adabf4 100644 --- a/src/main/java/io/github/dsheirer/module/decode/am/AMDecoder.java +++ b/src/main/java/io/github/dsheirer/module/decode/am/AMDecoder.java @@ -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 @@ -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."); + } }