[WIP] Updates the AM decoder module.

Removes a number of unused classes in an effort to remove RealBuffer
usage across the codebase (has been replaced by ReusableBuffers).
This commit is contained in:
Denny
2018-06-30 05:37:13 -04:00
parent df1bc1031e
commit 6f1ce79461
24 changed files with 494 additions and 1986 deletions

View File

@@ -1,22 +1,83 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.am;
public abstract class AMDemodulator
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
import io.github.dsheirer.sample.buffer.ReusableComplexBuffer;
/**
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
*/
public class AMDemodulator
{
private float mGain;
public AMDemodulator( float gain )
{
mGain = gain;
}
public float demodulate( float inphase, float quadrature )
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("AMDemodulator");
private int mOutputBufferPointer;
private float mGain;
/**
* Constructs this demodulator where the specified gain is applied to demodulated output samples.
*
* @param gain to apply to demodulated output samples.
*/
public AMDemodulator(float gain)
{
return (float)Math.sqrt( ( inphase * inphase ) +
( quadrature * quadrature ) ) * mGain;
mGain = gain;
}
/**
* Sets the gain to the specified level.
*/
public void setGain(float gain)
{
mGain = gain;
}
/**
* Demodulates the comples I/Q sample and returns the demodulated output sample.
*
* @param inphase sample
* @param quadrature sample
* @return AM demodulated sample
*/
public float demodulate(float inphase, float quadrature)
{
return (float)Math.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
}
/**
* Demodulates the reusable comples sample buffer and returns a demodulated audio buffer.
* @param complexBuffer to demodulate
* @return demodulated audio buffer.
*/
public ReusableBuffer demodulate(ReusableComplexBuffer complexBuffer)
{
ReusableBuffer reusableBuffer = mReusableBufferQueue.getBuffer(complexBuffer.getSampleCount());
float[] input = complexBuffer.getSamples();
float[] output = reusableBuffer.getSamples();
mOutputBufferPointer = 0;
for(int x= 0; x < complexBuffer.getSamples().length; x += 2)
{
output[mOutputBufferPointer++] = demodulate(input[x], input[x + 1]);
}
complexBuffer.decrementUserCount();
reusableBuffer.incrementUserCount();
return reusableBuffer;
}
public void setGain( float gain )
{
mGain = gain;
}
}

View File

@@ -58,6 +58,16 @@ public class DCRemovalFilter
return filtered;
}
public float[] filter( float[] samples )
{
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = filter( samples[ x ] );
}
return samples;
}
/**
* Filters the buffer samples and returns a new reusable buffer with the filtered samples.
*

View File

@@ -1,61 +0,0 @@
package io.github.dsheirer.dsp.filter.dc;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.RealBuffer;
public class DCRemovalFilter_RB extends DCRemovalFilter
implements Listener<RealBuffer>
{
protected Listener<RealBuffer> mListener;
public DCRemovalFilter_RB( float ratio )
{
super( ratio );
}
public void setListener( Listener<RealBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
public void dispose()
{
mListener = null;
}
public float[] filter( float[] samples )
{
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = filter( samples[ x ] );
}
return samples;
}
public RealBuffer filter( RealBuffer buffer )
{
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = filter( samples[ x ] );
}
return buffer;
}
@Override
public void receive( RealBuffer buffer )
{
if( mListener != null )
{
mListener.receive( filter( buffer ) );
}
}
}

View File

@@ -1,60 +0,0 @@
package io.github.dsheirer.dsp.filter.dc;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.RealBuffer;
public class DCRemovalFilter_RealBuffer implements Listener<RealBuffer>
{
private Listener<RealBuffer> mListener;
/**
* Removes DC bias present across the samples in the buffer by calculating
* the mean DC component and subtracting that component from each of the
* samples.
*/
public DCRemovalFilter_RealBuffer()
{
}
public void dispose()
{
mListener = null;
}
public RealBuffer filter( RealBuffer buffer )
{
double accumulator = 0.0d;
for( float sample: buffer.getSamples() )
{
accumulator += sample;
}
float mean = (float)( accumulator / (double)buffer.getSamples().length );
for( float sample: buffer.getSamples() )
{
sample -= mean;
}
return buffer;
}
@Override
public void receive( RealBuffer buffer )
{
if( mListener != null )
{
mListener.receive( filter( buffer ) );
}
}
public void setListener( Listener<RealBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
}

View File

@@ -1,116 +0,0 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.dsp.filter.polyphase;
public class FilterStage
{
private float[] mBuffer;
private int mBufferSize;
private int mBufferPointer;
private int[][] mIndexMap;
private float[] mCoefficients;
/**
* Single stage of a polyphase filter implemented with a circular sample buffer that uses an index map to ensure
* correct alignment of samples to filter taps.
*
* @param coefficients
*/
public FilterStage(float[] coefficients)
{
mCoefficients = coefficients;
mBufferSize = mCoefficients.length;
mBuffer = new float[mBufferSize];
//Note: buffer pointer starts at an illegal index but is decremented before first usage
mBufferPointer = mBufferSize;
generateIndexMap(mBufferSize);
}
/**
* Coefficients used by this filter stage.
*/
public float[] getCoefficients()
{
return mCoefficients;
}
/**
* Cleanup prior to disposal
*/
public void dispose()
{
mCoefficients = null;
mIndexMap = null;
mBuffer = null;
}
/**
* Loads the sample into the sample buffer
*/
private void load(float sample)
{
mBufferPointer--;
if (mBufferPointer < 0)
{
mBufferPointer += mBufferSize;
}
mBuffer[mBufferPointer] = sample;
}
/**
* Applies convolution against the currently loaded buffer samples.
*/
public float filter(float sample)
{
load(sample);
float accumulator = 0.0f;
for (int x = 0; x < mBufferSize; x++)
{
accumulator += mCoefficients[x] * mBuffer[mIndexMap[mBufferPointer][x]];
}
return accumulator;
}
/**
* Generates a circular buffer index map to support lookup of the translated index based on the current buffer
* pointer and the desired sample index.
*/
private void generateIndexMap(int size)
{
mIndexMap = new int[size][size];
for (int x = 0; x < size; x++)
{
for (int y = 0; y < size; y++)
{
int z = x + y;
mIndexMap[x][y] = z < size ? z : z - size;
}
}
}
}

View File

@@ -1,206 +0,0 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.dsp.filter.polyphase;
import io.github.dsheirer.dsp.filter.design.FilterDesignException;
import io.github.dsheirer.dsp.filter.fir.FIRFilter;
import io.github.dsheirer.dsp.filter.fir.FIRFilterSpecification;
import io.github.dsheirer.dsp.filter.fir.remez.RemezFIRFilterDesigner;
import io.github.dsheirer.dsp.mixer.IOscillator;
import io.github.dsheirer.dsp.mixer.Oscillator;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.RealBuffer;
import io.github.dsheirer.sample.real.RealToRealBufferAssembler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class PolyphaseFIRDecimatingFilter_RB extends FIRFilter implements Listener<RealBuffer>
{
private final static Logger mLog = LoggerFactory.getLogger( PolyphaseFIRDecimatingFilter_RB.class );
private List<FilterStage> mFilterStages = new ArrayList<>();
private int mFilterStagePointer;
private Listener<RealBuffer> mListener;
private RealToRealBufferAssembler mAssembler;
private int mDecimationRatio;
private float mAccumulator = 0.0f;
private float mGain = 1.0f;
/**
* Polyphase filter stage for decimating real-valued sample buffers by the decimation ratio. Creates a number of
* polyphase filter arms/stages equal to the decimation ratio.
*
* Note: output real-valued buffer sizes are determined by the first input buffer that is presented to the filter
* where the output buffer size is equal to the floor of the input buffer size divided by the decimation ratio.
*
* @param coefficients of a low pass filter designed for the input sample rate with a cutoff frequency that conforms
* to the nyquist frequency for an output sample rate equal to the input sample rate divided by the decimation ratio.
*
* @param decimationRatio to decimate the input sample rate by.
* @param gain to apply to the filtered output. Use a value of 1.0 to apply no gain.
*/
public PolyphaseFIRDecimatingFilter_RB(float[] coefficients, int decimationRatio, float gain)
{
mDecimationRatio = decimationRatio;
mGain = gain;
createFilterStages(coefficients, mDecimationRatio);
mFilterStagePointer = mFilterStages.size() - 1;
}
/**
* Creates polyphase filter stages (arms) from the set of coefficients.
*
* @param coefficients for the low-pass filter
* @param decimationRatio to determine the number of polyphase filter stages.
*/
private void createFilterStages(float[] coefficients, int decimationRatio)
{
int tapSize = (int)Math.ceil((double)coefficients.length / (double)decimationRatio);
float[][] coefficientSets = new float[decimationRatio][tapSize];
int stagePointer = 0;
int coefficientPointer = 0;
//Split the coefficients up into polyphase stage coefficient sets
for(int x = 0; x < coefficients.length; x++)
{
coefficientSets[stagePointer++][coefficientPointer] = coefficients[x];
if(stagePointer >= decimationRatio)
{
stagePointer = 0;
coefficientPointer++;
}
}
//Create the stages
for(int x = 0; x < decimationRatio; x++)
{
mFilterStages.add(new FilterStage(coefficientSets[x]));
}
}
public void dispose()
{
mListener = null;
}
@Override
public void receive(RealBuffer buffer)
{
if(mAssembler == null)
{
mAssembler = new RealToRealBufferAssembler(buffer.getSamples().length / mDecimationRatio);
mAssembler.setListener(mListener);
}
for(float sample: buffer.getSamples())
{
filter(sample);
}
int a = 0;
}
private void filter(float sample)
{
FilterStage stage = mFilterStages.get(mFilterStagePointer--);
mAccumulator += stage.filter(sample);
if(mFilterStagePointer < 0)
{
mAssembler.receive(mAccumulator * mGain);
mAccumulator = 0.0f;
mFilterStagePointer = mFilterStages.size() - 1;
}
}
public void setListener(Listener<RealBuffer> listener)
{
mListener = listener;
if(mAssembler != null)
{
mAssembler.setListener(mListener);
}
}
public void removeListener()
{
mListener = null;
if(mAssembler != null)
{
mAssembler.setListener(null);
}
}
public static void main(String[] args)
{
FIRFilterSpecification specification = FIRFilterSpecification.lowPassBuilder()
.sampleRate( 48000 )
.gridDensity( 16 )
.passBandCutoff( 3000 )
.passBandAmplitude( 1.0 )
.passBandRipple( 0.01 )
.stopBandStart( 4000 )
.stopBandAmplitude( 0.0 )
.stopBandRipple( 0.027 )
.build();
RemezFIRFilterDesigner designer = new RemezFIRFilterDesigner(specification);
try
{
if(!designer.isValid())
{
throw new FilterDesignException("Couldn't design the filter");
}
float[] coefficients = designer.getImpulseResponse();
PolyphaseFIRDecimatingFilter_RB filter = new PolyphaseFIRDecimatingFilter_RB(coefficients, 6, 1.0f);
filter.setListener(new Listener<RealBuffer>()
{
@Override
public void receive(RealBuffer realBuffer)
{
mLog.info("Filtered: " + Arrays.toString(realBuffer.getSamples()));
}
});
IOscillator oscillator = new Oscillator(3400, 48000);
float[] samples = oscillator.generateReal(500);
RealBuffer buffer = new RealBuffer(samples);
filter.receive(buffer);
}
catch(FilterDesignException e)
{
mLog.debug("Error designing filter", e);
}
}
}

View File

@@ -1,203 +0,0 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package io.github.dsheirer.dsp.filter.polyphase;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Polyphase interpolating FIR filter with an internal circular buffer as
* described by the dspGuru at:
*
* http://www.dspguru.com/dsp/faqs/multirate/interpolation
* See sections 3.4.2 and 3.4.3 )
*/
public class PolyphaseFIRInterpolatingFilter
{
private final static Logger mLog =
LoggerFactory.getLogger( PolyphaseFIRInterpolatingFilter.class );
private double[] mBuffer;
private int mBufferPointer = 0;
private double[] mTaps;
private Phase[] mPhases;
/**
* Constructs the interpolating filter. Coefficients length must be an
* integer multiple of the interpolation value. Each sample processed by
* the filter will generate a quantity of samples equal to the interpolation
* value.
*
* @param coefficients - low pass filter coefficients sized for the desired
* interpolated sample rate with a cutoff frequency of no more than half of
* the original sample rate.
*
* @param interpolation - integral interpolation quantity
*/
public PolyphaseFIRInterpolatingFilter( double[] coefficients, int interpolation )
{
/* Ensure coefficients are an integral multiple of the interpolation factor */
Validate.isTrue(coefficients.length % interpolation == 0);
mTaps = coefficients;
mPhases = new Phase[ interpolation ];
int phaseSize = coefficients.length / interpolation;
mBuffer = new double[ phaseSize ];
for( int x = 0; x < interpolation; x++ )
{
int coefficientIndex = 0;
int[] indexes = new int[ phaseSize ];
for( int y = x; y < coefficients.length; y += interpolation )
{
indexes[ coefficientIndex++ ] = y;
}
/* Create a phase using the tap indexes and the interpolation value
* for the gain value, to offset the loss induced in upsampling from
* one sample to (interpolation) samples. */
mPhases[ interpolation - x - 1 ] = new Phase( indexes, interpolation );
}
}
/**
* Inserts the sample into the filter and returns (interpolation) number
* of interpolated samples in the return array.
*
* @param sample - float sample
* @return - array of interpolated samples, size = interpolation quantity
*/
public float[] interpolate( float sample )
{
mBuffer[ mBufferPointer ] = (double)sample;
mBufferPointer++;
mBufferPointer %= mBuffer.length;
float[] samples = new float[ mPhases.length ];
for( int x = 0; x < mPhases.length; x++ )
{
samples[ x ] = mPhases[ x ].filter();
}
return samples;
}
/**
* Upsamples and interpolates the array of samples, returning an array that
* is upsampled and low-pass filtered by the interpolation quantity.
*
* @param samples - array of float samples
* @return - array of interpolated samples, size = interpolation * samples size
*/
public float[] interpolate( float[] samples )
{
int pointer = 0;
float[] interpolated = new float[ samples.length * mPhases.length ];
for( float sample: samples )
{
mBuffer[ mBufferPointer ] = (double)sample;
mBufferPointer++;
mBufferPointer %= mBuffer.length;
for( int x = 0; x < mPhases.length; x++ )
{
interpolated[ pointer ] = mPhases[ x ].filter();
pointer++;
}
}
return interpolated;
}
/**
* Single phase element of a polyphase interpolating FIR filter. Each phase
* element shares a common buffer. The tap indexes argument defines the
* filter taps used by this phase element to generate an interpolated value
* from the shared circular buffer of samples.
*/
public class Phase
{
private int[] mIndexes;
private double mGain;
public Phase( int[] tapIndexes, int gain )
{
mIndexes = tapIndexes;
mGain = gain;
}
public float filter()
{
double accumulator = 0;
for( int x = 0; x < mIndexes.length; x++ )
{
accumulator += mTaps[ mIndexes[ x ] ] *
mBuffer[ ( x + mBufferPointer ) % mIndexes.length ];
}
return (float)( accumulator * mGain );
}
}
// public static void main( String[] args )
// {
// int interpolation = 6;
//
// PolyphaseFIRInterpolatingFilter p =
// new PolyphaseFIRInterpolatingFilter( IMBESynthesizer.INTERPOLATION_TAPS, interpolation );
//
// Oscillator o = new Oscillator( 500, 8000 );
//
// int iterations = 40;
//
// float[] in = new float[ iterations ];
// float[] out = new float[ iterations * interpolation ];
//
// int inIndex = 0;
// int outIndex = 0;
//
// for( int x = 0; x < iterations; x++ )
// {
// float next = o.nextFloat();
//
// in[ inIndex++ ] = next;
//
// float[] samples = p.interpolate( next );
//
// System.arraycopy( samples, 0, out, outIndex, samples.length );
//
// outIndex += interpolation;
// }
//
// mLog.debug( "In: " + Arrays.toString( in ) );
// mLog.debug( "Out: " + Arrays.toString( out ) );
// }
}

View File

@@ -2,32 +2,32 @@ package io.github.dsheirer.dsp.gain;
import io.github.dsheirer.buffer.DoubleCircularBuffer;
import io.github.dsheirer.buffer.RealCircularBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import io.github.dsheirer.sample.buffer.ReusableBuffer;
import io.github.dsheirer.sample.buffer.ReusableBufferQueue;
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
* Copyright (C) 2014 - 2018 Dennis Sheirer
* Copyright (C) 2011 Alex Csete
* Copyright (C) 2010 Moe Wheatley
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*
* ---------------------------------------------------
* Ported from Alex Csete's gqrx source code at:
* https://github.com/csete/gqrx/blob/74ba2742c89b0dfb66854fc9f72d19e5c6e355b6/dsp/agc_impl.cpp
*
*
* This Software is released under the "Simplified BSD License" + + +
* Copyright 2010 Moe Wheatley. All rights reserved.
*
@@ -60,165 +60,189 @@ import java.util.concurrent.atomic.AtomicBoolean;
******************************************************************************/
public class AutomaticGainControl
{
private static final double SAMPLE_RATE = 48000;
/* Signal delay line - time delay in seconds */
private static final double SAMPLE_RATE = 48000;
/* Signal delay line - time delay in seconds */
// private static final double DELAY_TIME_CONSTANT = 0.015;
private static final double DELAY_TIME_CONSTANT = 0.01;
private static final double DELAY_TIME_CONSTANT = 0.01;
/* Peak detector window - time delay in seconds */
private static final double WINDOW_TIME_CONSTANT = 0.018;
/* Peak detector window - time delay in seconds */
private static final double WINDOW_TIME_CONSTANT = 0.018;
/* Attack time constants in seconds */
private static final double ATTACK_RISE_TIME_CONSTANT = 0.002;
private static final double ATTACK_FALL_TIME_CONSTANT = 0.005;
/* Attack time constants in seconds */
private static final double ATTACK_RISE_TIME_CONSTANT = 0.002;
private static final double ATTACK_FALL_TIME_CONSTANT = 0.005;
private static final double ATTACK_RISE_ALPHA = 1.0 -
Math.exp( -1.0 / SAMPLE_RATE * ATTACK_RISE_TIME_CONSTANT );
private static final double ATTACK_RISE_ALPHA = 1.0 -
Math.exp(-1.0 / SAMPLE_RATE * ATTACK_RISE_TIME_CONSTANT);
private static final double ATTACK_FALL_ALPHA = 1.0 -
Math.exp( -1.0 / SAMPLE_RATE * ATTACK_FALL_TIME_CONSTANT );
private static final double ATTACK_FALL_ALPHA = 1.0 -
Math.exp(-1.0 / SAMPLE_RATE * ATTACK_FALL_TIME_CONSTANT);
/* AGC decay value in milliseconds (20 to 5000) */
private static final double DECAY = 200;
/* Ratio between rise and fall times of decay time constants - adjust for
* best action with SSB */
private static final double DECAY_RISEFALL_RATIO = 0.3;
/* AGC decay value in milliseconds (20 to 5000) */
private static final double DECAY = 200;
private static final double DECAY_RISE_ALPHA = 1.0 -
Math.exp( -1.0 / ( SAMPLE_RATE * DECAY * .001 * DECAY_RISEFALL_RATIO ) );
/* Ratio between rise and fall times of decay time constants - adjust for
* best action with SSB */
private static final double DECAY_RISEFALL_RATIO = 0.3;
private static final double DECAY_FALL_ALPHA = 1.0 -
Math.exp( -1.0 / ( SAMPLE_RATE * DECAY * .001 ) );
private static final double DECAY_RISE_ALPHA = 1.0 -
Math.exp(-1.0 / (SAMPLE_RATE * DECAY * .001 * DECAY_RISEFALL_RATIO));
/* Hang timer release decay time constant in seconds */
@SuppressWarnings( "unused" )
private static final double RELEASE_TIME_CONSTANT = 0.05;
private static final double DECAY_FALL_ALPHA = 1.0 -
Math.exp(-1.0 / (SAMPLE_RATE * DECAY * .001));
/* Specifies the AGC Knee in dB if AGC is active (nominal range -160 to 0 dB) */
private static final double THRESHOLD = -100;
/* Hang timer release decay time constant in seconds */
@SuppressWarnings("unused")
private static final double RELEASE_TIME_CONSTANT = 0.05;
/* Limit output to about 3db of maximum */
private static final double AGC_OUT_SCALE = 0.7;
/* Specifies the AGC Knee in dB if AGC is active (nominal range -160 to 0 dB) */
private static final double THRESHOLD = -100;
/* Keep max input and output the same */
private static final double MAX_AMPLITUDE = 32767.0; //1.0;
private static final double MAX_MANUAL_AMPLITUDE = 32767.0; //1.0;
/* Specifies AGC manual gain in dB if AGC is not active ( 0 to 100 dB) */
private static final double MANUAL_GAIN = 0.0;
/* Limit output to about 3db of maximum */
private static final double AGC_OUT_SCALE = 0.7;
private static final double MANUAL_AGC_GAIN = MAX_MANUAL_AMPLITUDE *
Math.pow( 10.0, MANUAL_GAIN / 20.0 );
/* Keep max input and output the same */
private static final double MAX_AMPLITUDE = 32767.0; //1.0;
private static final double MAX_MANUAL_AMPLITUDE = 32767.0; //1.0;
/* Specifies dB reduction in output at knee from max output level (0 - 10dB) */
private static final double SLOPE_FACTOR = 2.0;
/* Specifies AGC manual gain in dB if AGC is not active ( 0 to 100 dB) */
private static final double MANUAL_GAIN = 0.0;
private static final double KNEE = THRESHOLD / 20.0;
private static final double MANUAL_AGC_GAIN = MAX_MANUAL_AMPLITUDE *
Math.pow(10.0, MANUAL_GAIN / 20.0);
private static final double GAIN_SLOPE = SLOPE_FACTOR / 100.0;
/* Specifies dB reduction in output at knee from max output level (0 - 10dB) */
private static final double SLOPE_FACTOR = 2.0;
private static final double FIXED_GAIN = AGC_OUT_SCALE *
Math.pow( 10.0, KNEE * ( GAIN_SLOPE - 1.0 ) );
/* Constant for calc log() so that a value of 0 magnitude = -8 */
private static final double MIN_CONSTANT = 3.2767E-4;
private AtomicBoolean mAGCEnabled = new AtomicBoolean( true );
private double mPeakMagnitude = 0.0;
private double mAttackAverage = 0.0;
private double mDecayAverage = 0.0;
private RealCircularBuffer mDelayBuffer =
new RealCircularBuffer( (int)( SAMPLE_RATE * DELAY_TIME_CONSTANT ) );
private DoubleCircularBuffer mMagnitudeBuffer =
new DoubleCircularBuffer( (int)( SAMPLE_RATE * WINDOW_TIME_CONSTANT ) );
public AutomaticGainControl()
{
}
private static final double KNEE = THRESHOLD / 20.0;
public float process( float currentSample )
private static final double GAIN_SLOPE = SLOPE_FACTOR / 100.0;
private static final double FIXED_GAIN = AGC_OUT_SCALE *
Math.pow(10.0, KNEE * (GAIN_SLOPE - 1.0));
/* Constant for calc log() so that a value of 0 magnitude = -8 */
private static final double MIN_CONSTANT = 3.2767E-4;
private boolean mAGCEnabled = true;
private double mPeakMagnitude = 0.0;
private double mAttackAverage = 0.0;
private double mDecayAverage = 0.0;
private RealCircularBuffer mDelayBuffer = new RealCircularBuffer((int)(SAMPLE_RATE * DELAY_TIME_CONSTANT));
private DoubleCircularBuffer mMagnitudeBuffer = new DoubleCircularBuffer((int)(SAMPLE_RATE * WINDOW_TIME_CONSTANT));
private ReusableBufferQueue mReusableBufferQueue = new ReusableBufferQueue("AutomaticGainControl");
public AutomaticGainControl()
{
float delayedSample = mDelayBuffer.putAndGet( currentSample );
double gain = MANUAL_AGC_GAIN;
if( mAGCEnabled.get() )
{
double currentMagnitude = Math.log10( Math.abs( currentSample ) +
MIN_CONSTANT ) - Math.log10( MAX_AMPLITUDE );
double delayedMagnitude = mMagnitudeBuffer.get( currentMagnitude );
if( currentMagnitude > mPeakMagnitude )
{
/* Use current magnitude as peak if it's larger */
mPeakMagnitude = currentMagnitude;
}
else if( delayedMagnitude == mPeakMagnitude )
{
/* If delayed magnitude is the current peak, then find a new peak */
mPeakMagnitude = mMagnitudeBuffer.max();
}
/* Exponential decay mode */
if( mPeakMagnitude > mAttackAverage )
{
mAttackAverage = ( ( 1.0 - ATTACK_RISE_ALPHA ) * mAttackAverage ) +
( ATTACK_RISE_ALPHA * mPeakMagnitude );
}
else
{
mAttackAverage = ( ( 1.0 - ATTACK_FALL_ALPHA ) * mAttackAverage ) +
( ATTACK_FALL_ALPHA * mPeakMagnitude );
}
if( mPeakMagnitude > mDecayAverage )
{
mDecayAverage = ( ( 1.0 - DECAY_RISE_ALPHA ) * mDecayAverage ) +
( DECAY_RISE_ALPHA * mPeakMagnitude );
}
else
{
mDecayAverage = ( ( 1.0 - DECAY_FALL_ALPHA ) * mDecayAverage ) +
( DECAY_RISE_ALPHA * mPeakMagnitude );
}
double magnitude = ( mAttackAverage > mDecayAverage ) ?
mAttackAverage : mDecayAverage;
if( magnitude < KNEE )
{
gain = FIXED_GAIN;
}
else
{
gain = AGC_OUT_SCALE * Math.pow( 10.0,
magnitude * ( GAIN_SLOPE - 1.0 ) );
}
}
return (float)( delayedSample * gain );
}
/**
* Enables or disables Automatic Gain Control (AGC).
*/
public void setAGCEnabled( boolean enabled )
{
mAGCEnabled.set( enabled );
}
/**
* Indicates if AGC is enabled
* @return true=AGC, false=MANUAL GAIN
*/
public boolean isAGCEnabled()
{
return mAGCEnabled.get();
}
/**
* Applies gain to the samples contained in the input buffer and returns a new buffer containing the
* processed samples.
*
* @param input samples to process
* @return output samples with gain applied
*/
public ReusableBuffer process(ReusableBuffer input)
{
ReusableBuffer output = mReusableBufferQueue.getBuffer(input.getSampleCount());
float[] inputSamples = input.getSamples();
float[] outputSamples = output.getSamples();
for(int x = 0; x < inputSamples.length; x++)
{
outputSamples[x] = process(inputSamples[x]);
}
input.decrementUserCount();
output.incrementUserCount();
return output;
}
/**
* Processes the input sample and applies gain
*
* @param currentSample to process
* @return gain applied sample
*/
public float process(float currentSample)
{
float delayedSample = mDelayBuffer.putAndGet(currentSample);
double gain = MANUAL_AGC_GAIN;
if(mAGCEnabled)
{
double currentMagnitude = Math.log10(Math.abs(currentSample) + MIN_CONSTANT) - Math.log10(MAX_AMPLITUDE);
double delayedMagnitude = mMagnitudeBuffer.get(currentMagnitude);
if(currentMagnitude > mPeakMagnitude)
{
/* Use current magnitude as peak if it's larger */
mPeakMagnitude = currentMagnitude;
}
else if(delayedMagnitude == mPeakMagnitude)
{
/* If delayed magnitude is the current peak, then find a new peak */
mPeakMagnitude = mMagnitudeBuffer.max();
}
/* Exponential decay mode */
if(mPeakMagnitude > mAttackAverage)
{
mAttackAverage = ((1.0 - ATTACK_RISE_ALPHA) * mAttackAverage) + (ATTACK_RISE_ALPHA * mPeakMagnitude);
}
else
{
mAttackAverage = ((1.0 - ATTACK_FALL_ALPHA) * mAttackAverage) + (ATTACK_FALL_ALPHA * mPeakMagnitude);
}
if(mPeakMagnitude > mDecayAverage)
{
mDecayAverage = ((1.0 - DECAY_RISE_ALPHA) * mDecayAverage) + (DECAY_RISE_ALPHA * mPeakMagnitude);
}
else
{
mDecayAverage = ((1.0 - DECAY_FALL_ALPHA) * mDecayAverage) + (DECAY_RISE_ALPHA * mPeakMagnitude);
}
double magnitude = (mAttackAverage > mDecayAverage) ? mAttackAverage : mDecayAverage;
if(magnitude < KNEE)
{
gain = FIXED_GAIN;
}
else
{
gain = AGC_OUT_SCALE * Math.pow(10.0, magnitude * (GAIN_SLOPE - 1.0));
}
}
return (float)(delayedSample * gain);
}
/**
* Enables or disables Automatic Gain Control (AGC).
*/
public void setAGCEnabled(boolean enabled)
{
mAGCEnabled = enabled;
}
/**
* Indicates if AGC is enabled
*
* @return true=AGC, false=MANUAL GAIN
*/
public boolean isAGCEnabled()
{
return mAGCEnabled;
}
}

View File

@@ -1,53 +0,0 @@
package io.github.dsheirer.dsp.gain;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.sample.real.RealBuffer;
public class AutomaticGainControl_RB extends AutomaticGainControl
implements Listener<RealBuffer>
{
private Listener<RealBuffer> mListener;
public AutomaticGainControl_RB()
{
}
public void dispose()
{
mListener = null;
}
/**
* Applies AGC to a buffer of real (float) samples
*/
public RealBuffer process( RealBuffer buffer )
{
float[] samples = buffer.getSamples();
for( int x = 0; x < samples.length; x++ )
{
samples[ x ] = process( samples[ x ] );
}
return buffer;
}
@Override
public void receive( RealBuffer buffer )
{
if( mListener != null )
{
mListener.receive( process( buffer ) );
}
}
public void setListener( Listener<RealBuffer> listener )
{
mListener = listener;
}
public void removeListener()
{
mListener = null;
}
}