mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
#35 Updates AM & NBFM decoders to use Adaptive Squelch, a power squelch that includes an adaptive noise floor monitor. Implements vectorized magnitude operations for AM & FM decoders. Adds talkgroup assignment and squelch controls to AM channel configuration editor. Implements new AM gain control that attempts to normalize audio gain quickly at beginning of call/transmission and maintains that gain across lifecycle of the call and resets the gain for the next call. (#1540)
Notes on usage: - When using either AM or FM squelch control use either the Heterodyne channelizer or if using the Polyphase channelizer, avoid using trunk tracking decoders like DMR or P25 or set the squelch threshold high enough to avoid changes in noise floor from polyphase channelizer where it can vary between using the one channel or the two-channel combined channel sources. Co-authored-by: Dennis Sheirer <dsheirer@github.com>
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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;
|
||||
|
||||
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
|
||||
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
|
||||
import io.github.dsheirer.vector.calibrate.Calibration;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationException;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||
import org.apache.commons.math3.stat.descriptive.moment.Mean;
|
||||
|
||||
/**
|
||||
* Calibrates AM demodulator options
|
||||
*/
|
||||
public class AmDemodulatorCalibration extends Calibration
|
||||
{
|
||||
private static final int BUFFER_SIZE = 2048;
|
||||
private static final int ITERATION_DURATION_MS = 1000;
|
||||
private static final int WARMUP_ITERATIONS = 5;
|
||||
private static final int TEST_ITERATIONS = 5;
|
||||
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
|
||||
private IAmDemodulator mScalarDemodulator = new ScalarAMDemodulator(500.0f);
|
||||
private IAmDemodulator mVectorDemodulator64 = new VectorAMDemodulator64(500.0f);
|
||||
private IAmDemodulator mVectorDemodulator128 = new VectorAMDemodulator128(500.0f);
|
||||
private IAmDemodulator mVectorAMDemodulator256 = new VectorAMDemodulator256(500.0f);
|
||||
private IAmDemodulator mVectorAMDemodulator512 = new VectorAMDemodulator512(500.0f);
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public AmDemodulatorCalibration()
|
||||
{
|
||||
super(CalibrationType.AM_DEMODULATOR);
|
||||
}
|
||||
|
||||
@Override public void calibrate() throws CalibrationException
|
||||
{
|
||||
float[] magnitude = getFloatSamples(BUFFER_SIZE);
|
||||
|
||||
Mean scalarMean = new Mean();
|
||||
|
||||
for(int x = 0; x < WARMUP_ITERATIONS; x++)
|
||||
{
|
||||
long score = testScalar(magnitude);
|
||||
scalarMean.increment(score);
|
||||
}
|
||||
|
||||
mLog.info("AM DEMODULATOR WARMUP - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
|
||||
|
||||
Mean vector64Mean = new Mean();
|
||||
|
||||
for(int x = 0; x < WARMUP_ITERATIONS; x++)
|
||||
{
|
||||
long score = testVector64(magnitude);
|
||||
vector64Mean.increment(score);
|
||||
}
|
||||
|
||||
mLog.info("AM DEMODULATOR WARMUP - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult()));
|
||||
|
||||
Mean vector128Mean = new Mean();
|
||||
|
||||
for(int x = 0; x < WARMUP_ITERATIONS; x++)
|
||||
{
|
||||
long score = testVector128(magnitude);
|
||||
vector128Mean.increment(score);
|
||||
}
|
||||
|
||||
mLog.info("AM DEMODULATOR WARMUP - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult()));
|
||||
|
||||
Mean vector256Mean = new Mean();
|
||||
|
||||
for(int x = 0; x < WARMUP_ITERATIONS; x++)
|
||||
{
|
||||
long score = testVector256(magnitude);
|
||||
vector256Mean.increment(score);
|
||||
}
|
||||
|
||||
mLog.info("AM DEMODULATOR WARMUP - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult()));
|
||||
|
||||
Mean vector512Mean = new Mean();
|
||||
|
||||
for(int x = 0; x < WARMUP_ITERATIONS; x++)
|
||||
{
|
||||
long score = testVector512(magnitude);
|
||||
vector512Mean.increment(score);
|
||||
}
|
||||
|
||||
mLog.info("AM DEMODULATOR WARMUP - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult()));
|
||||
|
||||
//Start tests
|
||||
scalarMean.clear();
|
||||
|
||||
for(int x = 0; x < TEST_ITERATIONS; x++)
|
||||
{
|
||||
long score = testScalar(magnitude);
|
||||
scalarMean.increment(score);
|
||||
}
|
||||
|
||||
mLog.info("AM DEMODULATOR - SCALAR: " + DECIMAL_FORMAT.format(scalarMean.getResult()));
|
||||
|
||||
vector64Mean.clear();
|
||||
|
||||
for(int x = 0; x < TEST_ITERATIONS; x++)
|
||||
{
|
||||
long score = testVector64(magnitude);
|
||||
vector64Mean.increment(score);
|
||||
}
|
||||
|
||||
mLog.info("AM DEMODULATOR - VECTOR 64: " + DECIMAL_FORMAT.format(vector64Mean.getResult()));
|
||||
|
||||
vector128Mean.clear();
|
||||
|
||||
for(int x = 0; x < TEST_ITERATIONS; x++)
|
||||
{
|
||||
long score = testVector128(magnitude);
|
||||
vector128Mean.increment(score);
|
||||
}
|
||||
|
||||
mLog.info("AM DEMODULATOR - VECTOR 128: " + DECIMAL_FORMAT.format(vector128Mean.getResult()));
|
||||
|
||||
vector256Mean.clear();
|
||||
|
||||
for(int x = 0; x < TEST_ITERATIONS; x++)
|
||||
{
|
||||
long score = testVector256(magnitude);
|
||||
vector256Mean.increment(score);
|
||||
}
|
||||
|
||||
mLog.info("AM DEMODULATOR - VECTOR 256: " + DECIMAL_FORMAT.format(vector256Mean.getResult()));
|
||||
|
||||
vector512Mean.clear();
|
||||
|
||||
for(int x = 0; x < TEST_ITERATIONS; x++)
|
||||
{
|
||||
long score = testVector512(magnitude);
|
||||
vector512Mean.increment(score);
|
||||
}
|
||||
|
||||
mLog.info("AM DEMODULATOR - VECTOR 512: " + DECIMAL_FORMAT.format(vector512Mean.getResult()));
|
||||
|
||||
Implementation optimal = Implementation.SCALAR;
|
||||
double optimalResult = scalarMean.getResult();
|
||||
|
||||
if(vector64Mean.getResult() > optimalResult)
|
||||
{
|
||||
optimal = Implementation.VECTOR_SIMD_64;
|
||||
optimalResult = vector64Mean.getResult();
|
||||
}
|
||||
|
||||
if(vector128Mean.getResult() > optimalResult)
|
||||
{
|
||||
optimal = Implementation.VECTOR_SIMD_128;
|
||||
optimalResult = vector128Mean.getResult();
|
||||
}
|
||||
|
||||
if(vector256Mean.getResult() > optimalResult)
|
||||
{
|
||||
optimal = Implementation.VECTOR_SIMD_256;
|
||||
optimalResult = vector256Mean.getResult();
|
||||
}
|
||||
|
||||
if(vector512Mean.getResult() > optimalResult)
|
||||
{
|
||||
optimal = Implementation.VECTOR_SIMD_512;
|
||||
}
|
||||
|
||||
setImplementation(optimal);
|
||||
|
||||
mLog.info("AM DEMODULATOR - SET OPTIMAL IMPLEMENTATION TO:" + getImplementation());
|
||||
}
|
||||
|
||||
private long testScalar(float[] magnitude)
|
||||
{
|
||||
|
||||
double accumulator = 0.0;
|
||||
long count = 0;
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
|
||||
{
|
||||
float[] demodulated = mScalarDemodulator.demodulateMagnitude(magnitude);
|
||||
accumulator += demodulated[1];
|
||||
count++;
|
||||
}
|
||||
|
||||
return count + (long)(accumulator * 0);
|
||||
}
|
||||
|
||||
private long testVector64(float[] magnitude)
|
||||
{
|
||||
double accumulator = 0.0;
|
||||
long count = 0;
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
|
||||
{
|
||||
float[] demodulated = mVectorDemodulator64.demodulateMagnitude(magnitude);
|
||||
accumulator += demodulated[1];
|
||||
count++;
|
||||
}
|
||||
|
||||
return count + (long)(accumulator * 0);
|
||||
}
|
||||
|
||||
private long testVector128(float[] magnitude)
|
||||
{
|
||||
double accumulator = 0.0;
|
||||
long count = 0;
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
|
||||
{
|
||||
float[] demodulated = mVectorDemodulator128.demodulateMagnitude(magnitude);
|
||||
accumulator += demodulated[1];
|
||||
count++;
|
||||
}
|
||||
|
||||
return count + (long)(accumulator * 0);
|
||||
}
|
||||
|
||||
private long testVector256(float[] magnitude)
|
||||
{
|
||||
double accumulator = 0.0;
|
||||
long count = 0;
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
|
||||
{
|
||||
float[] demodulated = mVectorAMDemodulator256.demodulateMagnitude(magnitude);
|
||||
accumulator += demodulated[1];
|
||||
count++;
|
||||
}
|
||||
|
||||
return count + (long)(accumulator * 0);
|
||||
}
|
||||
|
||||
private long testVector512(float[] magnitude)
|
||||
{
|
||||
double accumulator = 0.0;
|
||||
long count = 0;
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
while((System.currentTimeMillis() - start) < ITERATION_DURATION_MS)
|
||||
{
|
||||
float[] demodulated = mVectorAMDemodulator512.demodulateMagnitude(magnitude);
|
||||
accumulator += demodulated[1];
|
||||
count++;
|
||||
}
|
||||
|
||||
return count + (long)(accumulator * 0);
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
AmDemodulatorCalibration calibration = new AmDemodulatorCalibration();
|
||||
|
||||
try
|
||||
{
|
||||
calibration.calibrate();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error during calibration", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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;
|
||||
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Factory for creating AM demodulators
|
||||
*/
|
||||
public class AmDemodulatorFactory
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(AmDemodulatorFactory.class);
|
||||
|
||||
/**
|
||||
* Creates the optimal AM demodulator using calibration data to select the optimal
|
||||
* implementation from scalar and vector options.
|
||||
* @return demodulator instance
|
||||
*/
|
||||
public static IAmDemodulator getAmDemodulator(float gain)
|
||||
{
|
||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.AM_DEMODULATOR);
|
||||
|
||||
switch(implementation)
|
||||
{
|
||||
case VECTOR_SIMD_64:
|
||||
return new VectorAMDemodulator64(gain);
|
||||
case VECTOR_SIMD_128:
|
||||
return new VectorAMDemodulator128(gain);
|
||||
case VECTOR_SIMD_256:
|
||||
return new VectorAMDemodulator256(gain);
|
||||
case VECTOR_SIMD_512:
|
||||
return new VectorAMDemodulator512(gain);
|
||||
case VECTOR_SIMD_PREFERRED:
|
||||
return new VectorAMDemodulator64(gain);
|
||||
case SCALAR:
|
||||
case UNCALIBRATED:
|
||||
return new ScalarAMDemodulator(gain);
|
||||
default:
|
||||
mLog.warn("Unrecognized optimal operation for AM demodulator: " + implementation.name());
|
||||
return new ScalarAMDemodulator(gain);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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;
|
||||
|
||||
/**
|
||||
@@ -6,10 +25,9 @@ package io.github.dsheirer.dsp.am;
|
||||
public interface IAmDemodulator
|
||||
{
|
||||
/**
|
||||
* Demodulate the complex I & Q sample arrays
|
||||
* @param i samples
|
||||
* @param q samples
|
||||
* @return demodulated samples.
|
||||
* Demodulates an array of vector squared magnitudes.
|
||||
* @param magnitude values that are squared.
|
||||
* @return demodulated samples
|
||||
*/
|
||||
float[] demodulate(float[] i, float[] q);
|
||||
float[] demodulateMagnitude(float[] magnitude);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 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
|
||||
@@ -23,7 +23,7 @@ import org.apache.commons.math3.util.FastMath;
|
||||
/**
|
||||
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
|
||||
*/
|
||||
public class AMDemodulator implements IAmDemodulator
|
||||
public class ScalarAMDemodulator implements IAmDemodulator
|
||||
{
|
||||
private float mGain;
|
||||
|
||||
@@ -32,7 +32,7 @@ public class AMDemodulator implements IAmDemodulator
|
||||
*
|
||||
* @param gain to apply to demodulated output samples.
|
||||
*/
|
||||
public AMDemodulator(float gain)
|
||||
public ScalarAMDemodulator(float gain)
|
||||
{
|
||||
mGain = gain;
|
||||
}
|
||||
@@ -57,13 +57,13 @@ public class AMDemodulator implements IAmDemodulator
|
||||
return (float) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
|
||||
}
|
||||
|
||||
@Override public float[] demodulate(float[] i, float[] q)
|
||||
@Override
|
||||
public float[] demodulateMagnitude(float[] magnitude)
|
||||
{
|
||||
float[] demodulated = new float[i.length];
|
||||
|
||||
for(int x = 0; x < i.length; x++)
|
||||
float[] demodulated = new float[magnitude.length];
|
||||
for(int x = 0; x < magnitude.length; x++)
|
||||
{
|
||||
demodulated[x] = (float)FastMath.sqrt((i[x] * i[x]) + (q[x] * q[x])) * mGain;
|
||||
demodulated[x] = (float)FastMath.sqrt(magnitude[x]) * mGain;
|
||||
}
|
||||
|
||||
return demodulated;
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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;
|
||||
|
||||
import io.github.dsheirer.dsp.fm.ISquelchingDemodulator;
|
||||
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
|
||||
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
|
||||
import io.github.dsheirer.dsp.squelch.AdaptiveSquelch;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
|
||||
/**
|
||||
* AM demodulator with integrated squelch control.
|
||||
*/
|
||||
public class SquelchingAMDemodulator implements ISquelchingDemodulator, Listener<SourceEvent>
|
||||
{
|
||||
private static final float ZERO = 0.0f;
|
||||
private final IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
|
||||
private final IAmDemodulator mAmDemodulator;
|
||||
private final AdaptiveSquelch mAdaptiveSquelch;
|
||||
private boolean mSquelchChanged = false;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param gain to apply to the demodulated AM samples (e.g. 500.0f)
|
||||
* @param alpha decay value of the single pole IIR filter in range: 0.0 - 1.0. The smaller the alpha value,
|
||||
* the slower the squelch response.
|
||||
* @param squelchThreshold in decibels. Signal power must exceed this threshold value for unsquelch.
|
||||
* @param squelchAutoTrack to enable the squelch noise floor auto tracking feature.
|
||||
*/
|
||||
public SquelchingAMDemodulator(float gain, float alpha, float squelchThreshold, boolean squelchAutoTrack)
|
||||
{
|
||||
mAmDemodulator = AmDemodulatorFactory.getAmDemodulator(gain);
|
||||
mAdaptiveSquelch = new AdaptiveSquelch(alpha, squelchThreshold, squelchAutoTrack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or update the sample rate for the squelch to adjust the power level notification rate.
|
||||
* @param sampleRate in hertz
|
||||
*/
|
||||
public void setSampleRate(int sampleRate)
|
||||
{
|
||||
mAdaptiveSquelch.setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive notifications of squelch change events from the power squelch.
|
||||
*/
|
||||
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mAdaptiveSquelch.setSourceEventListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Demodulates the complex sample arrays.
|
||||
* @param i inphase sample array
|
||||
* @param q quadrature sample array
|
||||
* @return demodulated AM samples with gain applied.
|
||||
*/
|
||||
public float[] demodulate(float[] i, float[] q)
|
||||
{
|
||||
mAdaptiveSquelch.setSquelchChanged(false);
|
||||
setSquelchChanged(false);
|
||||
|
||||
float[] magnitude = mMagnitudeCalculator.calculate(i, q);
|
||||
float[] demodulated = mAmDemodulator.demodulateMagnitude(magnitude);
|
||||
|
||||
for(int x = 0; x < i.length; x++)
|
||||
{
|
||||
mAdaptiveSquelch.process(magnitude[x]);
|
||||
|
||||
if(!(mAdaptiveSquelch.isUnmuted()))
|
||||
{
|
||||
demodulated[x] = ZERO;
|
||||
}
|
||||
|
||||
if(mAdaptiveSquelch.isSquelchChanged())
|
||||
{
|
||||
setSquelchChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
return demodulated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the threshold for squelch control
|
||||
* @param threshold (dB)
|
||||
*/
|
||||
public void setSquelchThreshold(float threshold)
|
||||
{
|
||||
mAdaptiveSquelch.setSquelchThreshold(threshold);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSquelchAutoTrack(boolean autoTrack)
|
||||
{
|
||||
mAdaptiveSquelch.setSquelchAutoTrack(autoTrack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the squelch state has changed during the processing of buffer(s)
|
||||
*/
|
||||
public boolean isSquelchChanged()
|
||||
{
|
||||
return mSquelchChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or resets the squelch changed flag.
|
||||
*/
|
||||
private void setSquelchChanged(boolean changed)
|
||||
{
|
||||
mSquelchChanged = changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the squelch state is currently muted
|
||||
*/
|
||||
public boolean isMuted()
|
||||
{
|
||||
return mAdaptiveSquelch.isMuted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process source events initiated by the timer and end-user.
|
||||
* @param sourceEvent to process.
|
||||
*/
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
//Only forward squelch threshold & auto-track request events
|
||||
case REQUEST_CURRENT_SQUELCH_THRESHOLD:
|
||||
case REQUEST_CHANGE_SQUELCH_THRESHOLD:
|
||||
case REQUEST_CURRENT_SQUELCH_AUTO_TRACK:
|
||||
case REQUEST_CHANGE_SQUELCH_AUTO_TRACK:
|
||||
{
|
||||
mAdaptiveSquelch.receive(sourceEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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;
|
||||
|
||||
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
|
||||
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
|
||||
import io.github.dsheirer.vector.VectorUtilities;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
/**
|
||||
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
|
||||
*/
|
||||
public class VectorAMDemodulator128 implements IAmDemodulator
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||
private float mGain;
|
||||
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
|
||||
|
||||
/**
|
||||
* Constructs this demodulator where the specified gain is applied to demodulated output samples.
|
||||
*
|
||||
* @param gain to apply to demodulated output samples.
|
||||
*/
|
||||
public VectorAMDemodulator128(float gain)
|
||||
{
|
||||
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) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] demodulateMagnitude(float[] magnitude)
|
||||
{
|
||||
VectorUtilities.checkArrayLength(magnitude, VECTOR_SPECIES);
|
||||
FloatVector gain = FloatVector.broadcast(VECTOR_SPECIES, mGain);
|
||||
float[] demodulated = new float[magnitude.length];
|
||||
|
||||
FloatVector vector;
|
||||
for(int x = 0; x < magnitude.length; x += VECTOR_SPECIES.length())
|
||||
{
|
||||
vector = FloatVector.fromArray(VECTOR_SPECIES, magnitude, x);
|
||||
vector.sqrt().mul(gain).intoArray(demodulated, x);
|
||||
}
|
||||
|
||||
return demodulated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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;
|
||||
|
||||
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
|
||||
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
|
||||
import io.github.dsheirer.vector.VectorUtilities;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
/**
|
||||
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
|
||||
*/
|
||||
public class VectorAMDemodulator256 implements IAmDemodulator
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||
private float mGain;
|
||||
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
|
||||
|
||||
/**
|
||||
* Constructs this demodulator where the specified gain is applied to demodulated output samples.
|
||||
*
|
||||
* @param gain to apply to demodulated output samples.
|
||||
*/
|
||||
public VectorAMDemodulator256(float gain)
|
||||
{
|
||||
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) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] demodulateMagnitude(float[] magnitude)
|
||||
{
|
||||
VectorUtilities.checkArrayLength(magnitude, VECTOR_SPECIES);
|
||||
FloatVector gain = FloatVector.broadcast(VECTOR_SPECIES, mGain);
|
||||
float[] demodulated = new float[magnitude.length];
|
||||
|
||||
FloatVector vector;
|
||||
for(int x = 0; x < magnitude.length; x += VECTOR_SPECIES.length())
|
||||
{
|
||||
vector = FloatVector.fromArray(VECTOR_SPECIES, magnitude, x);
|
||||
vector.sqrt().mul(gain).intoArray(demodulated, x);
|
||||
}
|
||||
|
||||
return demodulated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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;
|
||||
|
||||
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
|
||||
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
|
||||
import io.github.dsheirer.vector.VectorUtilities;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
/**
|
||||
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
|
||||
*/
|
||||
public class VectorAMDemodulator512 implements IAmDemodulator
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||
private float mGain;
|
||||
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
|
||||
|
||||
/**
|
||||
* Constructs this demodulator where the specified gain is applied to demodulated output samples.
|
||||
*
|
||||
* @param gain to apply to demodulated output samples.
|
||||
*/
|
||||
public VectorAMDemodulator512(float gain)
|
||||
{
|
||||
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) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] demodulateMagnitude(float[] magnitude)
|
||||
{
|
||||
VectorUtilities.checkArrayLength(magnitude, VECTOR_SPECIES);
|
||||
FloatVector gain = FloatVector.broadcast(VECTOR_SPECIES, mGain);
|
||||
float[] demodulated = new float[magnitude.length];
|
||||
|
||||
FloatVector vector;
|
||||
for(int x = 0; x < magnitude.length; x += VECTOR_SPECIES.length())
|
||||
{
|
||||
vector = FloatVector.fromArray(VECTOR_SPECIES, magnitude, x);
|
||||
vector.sqrt().mul(gain).intoArray(demodulated, x);
|
||||
}
|
||||
|
||||
return demodulated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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;
|
||||
|
||||
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
|
||||
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
|
||||
import io.github.dsheirer.vector.VectorUtilities;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
/**
|
||||
* Performs AM demodulation on baseband I/Q samples to produce demodulated float output.
|
||||
*/
|
||||
public class VectorAMDemodulator64 implements IAmDemodulator
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||
private float mGain;
|
||||
private IMagnitudeCalculator mMagnitudeCalculator = MagnitudeFactory.getMagnitudeCalculator();
|
||||
|
||||
/**
|
||||
* Constructs this demodulator where the specified gain is applied to demodulated output samples.
|
||||
*
|
||||
* @param gain to apply to demodulated output samples.
|
||||
*/
|
||||
public VectorAMDemodulator64(float gain)
|
||||
{
|
||||
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) FastMath.sqrt((inphase * inphase) + (quadrature * quadrature)) * mGain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] demodulateMagnitude(float[] magnitude)
|
||||
{
|
||||
VectorUtilities.checkArrayLength(magnitude, VECTOR_SPECIES);
|
||||
FloatVector gain = FloatVector.broadcast(VECTOR_SPECIES, mGain);
|
||||
float[] demodulated = new float[magnitude.length];
|
||||
|
||||
FloatVector vector;
|
||||
for(int x = 0; x < magnitude.length; x += VECTOR_SPECIES.length())
|
||||
{
|
||||
vector = FloatVector.fromArray(VECTOR_SPECIES, magnitude, x);
|
||||
vector.sqrt().mul(gain).intoArray(demodulated, x);
|
||||
}
|
||||
|
||||
return demodulated;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.iir;
|
||||
|
||||
/**
|
||||
@@ -40,7 +59,7 @@ public class SinglePoleIirFilter
|
||||
* @param sample to process/filter
|
||||
* @return filtered output
|
||||
*/
|
||||
public double filter(float sample)
|
||||
public float filter(float sample)
|
||||
{
|
||||
mOutput = (mOutput * mOneMinusAlpha) + (mAlpha * sample);
|
||||
return mOutput;
|
||||
@@ -49,7 +68,7 @@ public class SinglePoleIirFilter
|
||||
/**
|
||||
* Current output value
|
||||
*/
|
||||
public double getValue()
|
||||
public float getValue()
|
||||
{
|
||||
return mOutput;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 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
|
||||
@@ -37,40 +37,23 @@ public class FmDemodulatorFactory
|
||||
* implementation from scalar and vector options.
|
||||
* @return demodulator instance
|
||||
*/
|
||||
public static IFmDemodulator getFmDemodulator()
|
||||
public static IDemodulator getFmDemodulator()
|
||||
{
|
||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.FM_DEMODULATOR);
|
||||
|
||||
switch(implementation)
|
||||
{
|
||||
case VECTOR_SIMD_PREFERRED:
|
||||
return new VectorFMDemodulator();
|
||||
case VECTOR_SIMD_64:
|
||||
return new VectorFMDemodulator64();
|
||||
case VECTOR_SIMD_128:
|
||||
return new VectorFMDemodulator128();
|
||||
case VECTOR_SIMD_256:
|
||||
return new VectorFMDemodulator256();
|
||||
case VECTOR_SIMD_512:
|
||||
return new VectorFMDemodulator512();
|
||||
case SCALAR:
|
||||
return new ScalarFMDemodulator();
|
||||
default:
|
||||
mLog.warn("Unrecognized optimal operation for FM demodulator: " + implementation.name());
|
||||
return new ScalarFMDemodulator();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the optimal Squelching FM demodulator using calibration data to select the optimal
|
||||
* implementation from scalar and vector options.
|
||||
* @return demodulator instance
|
||||
*/
|
||||
public static ISquelchingFmDemodulator getSquelchingFmDemodulator(float alpha, float threshold, int ramp)
|
||||
{
|
||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.FM_DEMODULATOR);
|
||||
|
||||
switch(implementation)
|
||||
{
|
||||
case VECTOR_SIMD_PREFERRED:
|
||||
return new VectorSquelchingFMDemodulator(alpha, threshold, ramp);
|
||||
case SCALAR:
|
||||
return new ScalarSquelchingFMDemodulator(alpha, threshold, ramp);
|
||||
default:
|
||||
mLog.warn("Unrecognized optimal operation for Squelching FM demodulator: " + implementation.name());
|
||||
return new ScalarSquelchingFMDemodulator(alpha, threshold, ramp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
src/main/java/io/github/dsheirer/dsp/fm/IDemodulator.java
Normal file
34
src/main/java/io/github/dsheirer/dsp/fm/IDemodulator.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.fm;
|
||||
|
||||
/**
|
||||
* Interface for demodulator of complex sample arrays.
|
||||
*/
|
||||
public interface IDemodulator
|
||||
{
|
||||
/**
|
||||
* Demodulate the complex sample array and return an array of real audio samples.
|
||||
* @param i samples to demodulate
|
||||
* @param q samples to demodulate
|
||||
* @return demodulated samples.
|
||||
*/
|
||||
float[] demodulate(float[] i, float[] q);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package io.github.dsheirer.dsp.fm;
|
||||
|
||||
/**
|
||||
* Interface for FM demodulator
|
||||
*/
|
||||
public interface IFmDemodulator
|
||||
{
|
||||
/**
|
||||
* Demodulate the complex sample array and return an array of real audio samples.
|
||||
* @param samples to demodulate
|
||||
* @return demodulated samples.
|
||||
*/
|
||||
float[] demodulate(float[] i, float[] q);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.fm;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
|
||||
/**
|
||||
* Interface for FM demodulator that provides squelch control.
|
||||
*/
|
||||
public interface ISquelchingDemodulator extends IDemodulator
|
||||
{
|
||||
/**
|
||||
* Sets the sample rate so that the squelch can adjust the channel power level broadcast notification rate
|
||||
* @param sampleRate in hertz
|
||||
*/
|
||||
void setSampleRate(int sampleRate);
|
||||
|
||||
/**
|
||||
* Indicates if the squelch state has changed.
|
||||
*/
|
||||
boolean isSquelchChanged();
|
||||
|
||||
/**
|
||||
* Indicates if the squelch state is currently muted
|
||||
*/
|
||||
boolean isMuted();
|
||||
|
||||
/**
|
||||
* Sets the threshold to use for power squelch
|
||||
* @param threshold in decibels
|
||||
*/
|
||||
void setSquelchThreshold(float threshold);
|
||||
|
||||
/**
|
||||
* Enables or disables the squelch auto-tracking feature where the squelch threshold is auto-adjusted based on the
|
||||
* observed channel power when the signal is not present (ie squelch state).
|
||||
* @param autoTrack true to enable
|
||||
*/
|
||||
void setSquelchAutoTrack(boolean autoTrack);
|
||||
|
||||
/**
|
||||
* Registers the listener to receive notifications of squelch change events from the power squelch.
|
||||
*/
|
||||
void setSourceEventListener(Listener<SourceEvent> listener);
|
||||
|
||||
/**
|
||||
* Receives a source event
|
||||
*/
|
||||
void receive(SourceEvent sourceEvent);
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package io.github.dsheirer.dsp.fm;
|
||||
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
|
||||
/**
|
||||
* Interface for FM demodulator that provides squelch control.
|
||||
*/
|
||||
public interface ISquelchingFmDemodulator extends IFmDemodulator
|
||||
{
|
||||
/**
|
||||
* Indicates if the squelch state has changed.
|
||||
*/
|
||||
boolean isSquelchChanged();
|
||||
|
||||
/**
|
||||
* Indicates if the squelch state is currently muted
|
||||
*/
|
||||
boolean isMuted();
|
||||
|
||||
/**
|
||||
* Sets the threshold to use for power squelch
|
||||
* @param threshold in decibels
|
||||
*/
|
||||
void setSquelchThreshold(double threshold);
|
||||
|
||||
/**
|
||||
* Resets the demodulator
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Registers the listener to receive notifications of squelch change events from the power squelch.
|
||||
*/
|
||||
void setSourceEventListener(Listener<SourceEvent> listener);
|
||||
|
||||
/**
|
||||
* Receives a source event
|
||||
*/
|
||||
void receive(SourceEvent sourceEvent);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 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
|
||||
@@ -18,16 +18,15 @@
|
||||
*/
|
||||
package io.github.dsheirer.dsp.fm;
|
||||
|
||||
import io.github.dsheirer.sample.complex.Complex;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
/**
|
||||
* FM Demodulator for demodulating complex samples and producing demodulated floating point samples.
|
||||
*/
|
||||
public class ScalarFMDemodulator implements IFmDemodulator
|
||||
public class ScalarFMDemodulator implements IDemodulator
|
||||
{
|
||||
private float mPreviousI = 0.0f;
|
||||
private float mPreviousQ = 0.0f;
|
||||
protected float mPreviousI = 0.0f;
|
||||
protected float mPreviousQ = 0.0f;
|
||||
protected float mGain;
|
||||
|
||||
/**
|
||||
@@ -47,50 +46,6 @@ public class ScalarFMDemodulator implements IFmDemodulator
|
||||
mGain = gain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Demodulates the I/Q sample via complex sample multiplication by the complex conjugates of the most recently
|
||||
* demodulated complex sample (ie previous sample). Each new sample overwrites the previously stored sample to
|
||||
* allow future invocations of the method to simply use a new sample value.
|
||||
*
|
||||
* @param currentI of the sample
|
||||
* @param currentQ of the sample
|
||||
* @return demodulated sample
|
||||
*/
|
||||
public float demodulate(float currentI, float currentQ)
|
||||
{
|
||||
/**
|
||||
* Multiply the current sample against the complex conjugate of the
|
||||
* previous sample to derive the phase delta between the two samples
|
||||
*
|
||||
* Negating the previous sample quadrature produces the conjugate
|
||||
*/
|
||||
double inphase = (currentI * mPreviousI) - (currentQ * -mPreviousQ);
|
||||
double quadrature = (currentQ * mPreviousI) + (currentI * -mPreviousQ);
|
||||
|
||||
double angle = 0.0f;
|
||||
|
||||
//Check for divide by zero
|
||||
if(inphase != 0)
|
||||
{
|
||||
/**
|
||||
* Use the arc-tangent of quadrature divided by inphase to
|
||||
* get the phase angle (+/-) which was directly manipulated by the
|
||||
* original message waveform during the modulation. This value now
|
||||
* serves as the instantaneous amplitude of the demodulated signal
|
||||
*/
|
||||
double denominator = 1.0d / inphase;
|
||||
angle = FastMath.atan((double)quadrature * denominator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current sample to use during the next iteration
|
||||
*/
|
||||
mPreviousI = currentI;
|
||||
mPreviousQ = currentQ;
|
||||
|
||||
return (float)(angle * mGain);
|
||||
}
|
||||
|
||||
public float[] demodulate(float[] i, float[] q)
|
||||
{
|
||||
float[] demodulated = new float[i.length];
|
||||
@@ -135,35 +90,6 @@ public class ScalarFMDemodulator implements IFmDemodulator
|
||||
return demodulated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Demodulates the complex samples and returns the demodulated value.
|
||||
* @param previous
|
||||
* @param current
|
||||
* @return
|
||||
*/
|
||||
public static double demodulate(Complex previous, Complex current)
|
||||
{
|
||||
double inphase = (current.inphase() * previous.inphase()) - (current.quadrature() * -previous.quadrature());
|
||||
double quadrature = (current.quadrature() * previous.inphase()) + (current.inphase() * -previous.quadrature());
|
||||
|
||||
double angle = 0.0f;
|
||||
|
||||
//Check for divide by zero
|
||||
if(inphase != 0)
|
||||
{
|
||||
/**
|
||||
* Use the arc-tangent of quadrature divided by inphase to
|
||||
* get the phase angle (+/-) which was directly manipulated by the
|
||||
* original message waveform during the modulation. This value now
|
||||
* serves as the instantaneous amplitude of the demodulated signal
|
||||
*/
|
||||
double denominator = 1.0d / inphase;
|
||||
angle = FastMath.atan((double)quadrature * denominator);
|
||||
}
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
//no-op
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.fm;
|
||||
|
||||
import io.github.dsheirer.dsp.squelch.PowerSquelch;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
|
||||
/**
|
||||
* FM Demodulator for demodulating complex samples and producing demodulated floating point samples.
|
||||
*
|
||||
* Implements listener of source events to process runtime squelch threshold change request events
|
||||
* which are forwarded to the power squelch control.
|
||||
*/
|
||||
public class ScalarSquelchingFMDemodulator extends ScalarFMDemodulator implements ISquelchingFmDemodulator, Listener<SourceEvent>
|
||||
{
|
||||
private PowerSquelch mPowerSquelch;
|
||||
private boolean mSquelchChanged = false;
|
||||
|
||||
/**
|
||||
* Creates an FM demodulator instance with a default gain of 1.0.
|
||||
*/
|
||||
public ScalarSquelchingFMDemodulator(float alpha, float threshold, int ramp)
|
||||
{
|
||||
mPowerSquelch = new PowerSquelch(alpha, threshold, ramp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive notifications of squelch change events from the power squelch.
|
||||
*/
|
||||
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mPowerSquelch.setSourceEventListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the threshold for squelch control
|
||||
* @param threshold (dB)
|
||||
*/
|
||||
public void setSquelchThreshold(double threshold)
|
||||
{
|
||||
mPowerSquelch.setSquelchThreshold(threshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the squelch state has changed during the processing of buffer(s)
|
||||
*/
|
||||
public boolean isSquelchChanged()
|
||||
{
|
||||
return mSquelchChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or resets the squelch changed flag.
|
||||
*/
|
||||
private void setSquelchChanged(boolean changed)
|
||||
{
|
||||
mSquelchChanged = changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the squelch state is currently muted
|
||||
*/
|
||||
public boolean isMuted()
|
||||
{
|
||||
return mPowerSquelch.isMuted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
//Only forward squelch threshold change request events
|
||||
if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD)
|
||||
{
|
||||
mPowerSquelch.receive(sourceEvent);
|
||||
}
|
||||
else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD)
|
||||
{
|
||||
mPowerSquelch.receive(sourceEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.fm;
|
||||
|
||||
import io.github.dsheirer.dsp.magnitude.IMagnitudeCalculator;
|
||||
import io.github.dsheirer.dsp.magnitude.MagnitudeFactory;
|
||||
import io.github.dsheirer.dsp.squelch.AdaptiveSquelch;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* FM Demodulator for demodulating complex samples and producing demodulated floating point samples.
|
||||
*
|
||||
* Implements listener of source events to process runtime squelch threshold change request events
|
||||
* which are forwarded to the power squelch control.
|
||||
*/
|
||||
public class SquelchingFMDemodulator implements ISquelchingDemodulator, Listener<SourceEvent>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(SquelchingFMDemodulator.class);
|
||||
private static final float ZERO = 0.0f;
|
||||
private final AdaptiveSquelch mAdaptiveSquelch;
|
||||
private final IMagnitudeCalculator mMagnitude = MagnitudeFactory.getMagnitudeCalculator();
|
||||
private final IDemodulator mFmDemodulator = FmDemodulatorFactory.getFmDemodulator();
|
||||
private boolean mSquelchChanged = false;
|
||||
|
||||
/**
|
||||
* Creates an FM demodulator instance with a default gain of 1.0.
|
||||
*
|
||||
* @param alpha decay value of the single pole IIR filter in range: 0.0 - 1.0. The smaller the alpha value,
|
||||
* the slower the squelch response.
|
||||
* @param squelchThreshold in decibels. Signal power must exceed this threshold value for unsquelch.
|
||||
* causes immediate mute and unmute. Set to higher count to prevent mute/unmute flapping.
|
||||
* @param squelchAutoTrack to enable the squelch noise floor auto-tracking feature.
|
||||
*/
|
||||
public SquelchingFMDemodulator(float alpha, float squelchThreshold, boolean squelchAutoTrack)
|
||||
{
|
||||
mAdaptiveSquelch = new AdaptiveSquelch(alpha, squelchThreshold, squelchAutoTrack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or update the sample rate for the squelch to adjust the power level notification rate.
|
||||
* @param sampleRate in hertz
|
||||
*/
|
||||
public void setSampleRate(int sampleRate)
|
||||
{
|
||||
mAdaptiveSquelch.setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive notifications of squelch change events from the power squelch.
|
||||
*/
|
||||
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mAdaptiveSquelch.setSourceEventListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Demodulates the complex (I/Q) sample arrays
|
||||
* @param i inphase samples
|
||||
* @param q quadrature samples
|
||||
* @return demodulated real samples
|
||||
*/
|
||||
@Override
|
||||
public float[] demodulate(float[] i, float[] q)
|
||||
{
|
||||
setSquelchChanged(false);
|
||||
|
||||
float[] demodulated = mFmDemodulator.demodulate(i, q);
|
||||
float[] magnitude = mMagnitude.calculate(i, q);
|
||||
|
||||
for(int x = 0; x < magnitude.length; x++)
|
||||
{
|
||||
mAdaptiveSquelch.process(magnitude[x]);
|
||||
|
||||
if(!(mAdaptiveSquelch.isUnmuted()))
|
||||
{
|
||||
demodulated[x] = ZERO;
|
||||
}
|
||||
|
||||
if(mAdaptiveSquelch.isSquelchChanged())
|
||||
{
|
||||
setSquelchChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
return demodulated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the threshold for squelch control
|
||||
* @param threshold (dB)
|
||||
*/
|
||||
public void setSquelchThreshold(float threshold)
|
||||
{
|
||||
mAdaptiveSquelch.setSquelchThreshold(threshold);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSquelchAutoTrack(boolean autoTrack)
|
||||
{
|
||||
mAdaptiveSquelch.setSquelchAutoTrack(autoTrack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the squelch state has changed during the processing of buffer(s)
|
||||
*/
|
||||
public boolean isSquelchChanged()
|
||||
{
|
||||
return mSquelchChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or resets the squelch changed flag.
|
||||
*/
|
||||
private void setSquelchChanged(boolean changed)
|
||||
{
|
||||
mSquelchChanged = changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the squelch state is currently muted
|
||||
*/
|
||||
public boolean isMuted()
|
||||
{
|
||||
return mAdaptiveSquelch.isMuted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
//Only forward squelch threshold and auto-track current & change requests
|
||||
case REQUEST_CURRENT_SQUELCH_THRESHOLD:
|
||||
case REQUEST_CHANGE_SQUELCH_THRESHOLD:
|
||||
case REQUEST_CURRENT_SQUELCH_AUTO_TRACK:
|
||||
case REQUEST_CHANGE_SQUELCH_AUTO_TRACK:
|
||||
mAdaptiveSquelch.receive(sourceEvent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 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
|
||||
@@ -26,17 +26,12 @@ import jdk.incubator.vector.VectorSpecies;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* FM demodulator that uses JDK 17+ SIMD vector intrinsics
|
||||
*/
|
||||
public class VectorFMDemodulator implements IFmDemodulator
|
||||
public abstract class VectorFMDemodulator implements IDemodulator
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(VectorFMDemodulator.class);
|
||||
protected static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_PREFERRED;
|
||||
protected static final float ZERO = 0.0f;
|
||||
private static final int BUFFER_OVERLAP = 1;
|
||||
|
||||
@@ -48,9 +43,16 @@ public class VectorFMDemodulator implements IFmDemodulator
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Vector species as specified by the implementing sub-class.
|
||||
*/
|
||||
protected abstract VectorSpecies<Float> getVectorSpecies();
|
||||
|
||||
@Override public float[] demodulate(float[] i, float[] q)
|
||||
{
|
||||
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||
// VectorSpecies<Float> species= getVectorSpecies();
|
||||
|
||||
VectorUtilities.checkComplexArrayLength(i, q, getVectorSpecies());
|
||||
|
||||
if(mIBuffer.length != (i.length + BUFFER_OVERLAP))
|
||||
{
|
||||
@@ -70,12 +72,12 @@ public class VectorFMDemodulator implements IFmDemodulator
|
||||
|
||||
FloatVector currentI, currentQ, previousI, previousQ, demod, demodI, demodQ;
|
||||
|
||||
for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += VECTOR_SPECIES.length())
|
||||
for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += getVectorSpecies().length())
|
||||
{
|
||||
previousI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer);
|
||||
previousQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer);
|
||||
currentI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer + 1);
|
||||
currentQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer + 1);
|
||||
previousI = FloatVector.fromArray(getVectorSpecies(), mIBuffer, bufferPointer);
|
||||
previousQ = FloatVector.fromArray(getVectorSpecies(), mQBuffer, bufferPointer);
|
||||
currentI = FloatVector.fromArray(getVectorSpecies(), mIBuffer, bufferPointer + 1);
|
||||
currentQ = FloatVector.fromArray(getVectorSpecies(), mQBuffer, bufferPointer + 1);
|
||||
|
||||
demodI = currentI.mul(previousI).sub(currentQ.mul(previousQ.neg()));
|
||||
demodQ = currentQ.mul(previousI).add(currentI.mul(previousQ.neg()));
|
||||
@@ -89,53 +91,4 @@ public class VectorFMDemodulator implements IFmDemodulator
|
||||
|
||||
return demodulated;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
int iterations = 3_000_000;
|
||||
int sampleSize = 2048;
|
||||
|
||||
Random random = new Random();
|
||||
|
||||
float[] iSamples = new float[sampleSize];
|
||||
float[] qSamples = new float[sampleSize];
|
||||
for(int x = 0; x < iSamples.length; x++)
|
||||
{
|
||||
iSamples[x] = random.nextFloat() * 2.0f - 1.0f;
|
||||
qSamples[x] = random.nextFloat() * 2.0f - 1.0f;
|
||||
}
|
||||
|
||||
boolean validation = false;
|
||||
|
||||
ScalarFMDemodulator legacy = new ScalarFMDemodulator();
|
||||
VectorFMDemodulator vector = new VectorFMDemodulator();
|
||||
|
||||
if(validation)
|
||||
{
|
||||
float[] legacySamples = legacy.demodulate(iSamples, qSamples);
|
||||
float[] vectorSamples = vector.demodulate(iSamples, qSamples);
|
||||
System.out.println("LEGACY:" + Arrays.toString(legacySamples));
|
||||
System.out.println("VECTOR:" + Arrays.toString(vectorSamples));
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("Test Starting ...");
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
double accumulator = 0.0;
|
||||
|
||||
for(int i = 0; i < iterations; i++)
|
||||
{
|
||||
float[] samples = legacy.demodulate(iSamples, qSamples);
|
||||
// float[] samples = vector.demodulate(iSamples, qSamples);
|
||||
accumulator += samples[3];
|
||||
}
|
||||
|
||||
double elapsed = System.currentTimeMillis() - start;
|
||||
|
||||
DecimalFormat df = new DecimalFormat("0.000");
|
||||
System.out.println("Accumulator: " + accumulator);
|
||||
System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.fm;
|
||||
|
||||
import io.github.dsheirer.vector.VectorUtilities;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* FM demodulator that uses JDK 17+ SIMD vector intrinsics
|
||||
*/
|
||||
public class VectorFMDemodulator128 implements IDemodulator
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(VectorFMDemodulator128.class);
|
||||
protected static final float ZERO = 0.0f;
|
||||
private static final int BUFFER_OVERLAP = 1;
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||
|
||||
//Initialize buffers to be non-null and resize once the first buffer arrives
|
||||
private float[] mIBuffer = new float[1];
|
||||
private float[] mQBuffer = new float[1];
|
||||
|
||||
public VectorFMDemodulator128()
|
||||
{
|
||||
}
|
||||
|
||||
@Override public float[] demodulate(float[] i, float[] q)
|
||||
{
|
||||
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||
|
||||
if(mIBuffer.length != (i.length + BUFFER_OVERLAP))
|
||||
{
|
||||
mIBuffer = new float[i.length + BUFFER_OVERLAP];
|
||||
mQBuffer = new float[q.length + BUFFER_OVERLAP];
|
||||
}
|
||||
|
||||
//Copy last sample to beginning of buffer arrays
|
||||
mIBuffer[0] = mIBuffer[mIBuffer.length - 1];
|
||||
mQBuffer[0] = mQBuffer[mQBuffer.length - 1];
|
||||
|
||||
//Copy new samples to end of buffer arrays
|
||||
System.arraycopy(i, 0, mIBuffer, 1, i.length);
|
||||
System.arraycopy(q, 0, mQBuffer, 1, q.length);
|
||||
|
||||
float[] demodulated = new float[i.length];
|
||||
|
||||
FloatVector currentI, currentQ, previousI, previousQ, demod, demodI, demodQ;
|
||||
|
||||
for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += VECTOR_SPECIES.length())
|
||||
{
|
||||
previousI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer);
|
||||
previousQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer);
|
||||
currentI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer + 1);
|
||||
currentQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer + 1);
|
||||
|
||||
demodI = currentI.mul(previousI).sub(currentQ.mul(previousQ.neg()));
|
||||
demodQ = currentQ.mul(previousI).add(currentI.mul(previousQ.neg()));
|
||||
|
||||
//Replace any zero-values in the I vector by adding Float.MIN_VALUE to side-step divide by zero errors
|
||||
demodI = demodI.add(Float.MIN_VALUE, demodQ.eq(ZERO));
|
||||
|
||||
demod = demodQ.div(demodI).lanewise(VectorOperators.ATAN);
|
||||
demod.intoArray(demodulated, bufferPointer);
|
||||
}
|
||||
|
||||
return demodulated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.fm;
|
||||
|
||||
import io.github.dsheirer.vector.VectorUtilities;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* FM demodulator that uses JDK 17+ SIMD vector intrinsics
|
||||
*/
|
||||
public class VectorFMDemodulator256 implements IDemodulator
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(VectorFMDemodulator256.class);
|
||||
protected static final float ZERO = 0.0f;
|
||||
private static final int BUFFER_OVERLAP = 1;
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||
|
||||
//Initialize buffers to be non-null and resize once the first buffer arrives
|
||||
private float[] mIBuffer = new float[1];
|
||||
private float[] mQBuffer = new float[1];
|
||||
|
||||
public VectorFMDemodulator256()
|
||||
{
|
||||
}
|
||||
|
||||
@Override public float[] demodulate(float[] i, float[] q)
|
||||
{
|
||||
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||
|
||||
if(mIBuffer.length != (i.length + BUFFER_OVERLAP))
|
||||
{
|
||||
mIBuffer = new float[i.length + BUFFER_OVERLAP];
|
||||
mQBuffer = new float[q.length + BUFFER_OVERLAP];
|
||||
}
|
||||
|
||||
//Copy last sample to beginning of buffer arrays
|
||||
mIBuffer[0] = mIBuffer[mIBuffer.length - 1];
|
||||
mQBuffer[0] = mQBuffer[mQBuffer.length - 1];
|
||||
|
||||
//Copy new samples to end of buffer arrays
|
||||
System.arraycopy(i, 0, mIBuffer, 1, i.length);
|
||||
System.arraycopy(q, 0, mQBuffer, 1, q.length);
|
||||
|
||||
float[] demodulated = new float[i.length];
|
||||
|
||||
FloatVector currentI, currentQ, previousI, previousQ, demod, demodI, demodQ;
|
||||
|
||||
for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += VECTOR_SPECIES.length())
|
||||
{
|
||||
previousI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer);
|
||||
previousQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer);
|
||||
currentI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer + 1);
|
||||
currentQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer + 1);
|
||||
|
||||
demodI = currentI.mul(previousI).sub(currentQ.mul(previousQ.neg()));
|
||||
demodQ = currentQ.mul(previousI).add(currentI.mul(previousQ.neg()));
|
||||
|
||||
//Replace any zero-values in the I vector by adding Float.MIN_VALUE to side-step divide by zero errors
|
||||
demodI = demodI.add(Float.MIN_VALUE, demodQ.eq(ZERO));
|
||||
|
||||
demod = demodQ.div(demodI).lanewise(VectorOperators.ATAN);
|
||||
demod.intoArray(demodulated, bufferPointer);
|
||||
}
|
||||
|
||||
return demodulated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.fm;
|
||||
|
||||
import io.github.dsheirer.vector.VectorUtilities;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* FM demodulator that uses JDK 17+ SIMD vector intrinsics
|
||||
*/
|
||||
public class VectorFMDemodulator512 implements IDemodulator
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(VectorFMDemodulator512.class);
|
||||
protected static final float ZERO = 0.0f;
|
||||
private static final int BUFFER_OVERLAP = 1;
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||
|
||||
//Initialize buffers to be non-null and resize once the first buffer arrives
|
||||
private float[] mIBuffer = new float[1];
|
||||
private float[] mQBuffer = new float[1];
|
||||
|
||||
public VectorFMDemodulator512()
|
||||
{
|
||||
}
|
||||
|
||||
@Override public float[] demodulate(float[] i, float[] q)
|
||||
{
|
||||
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||
|
||||
if(mIBuffer.length != (i.length + BUFFER_OVERLAP))
|
||||
{
|
||||
mIBuffer = new float[i.length + BUFFER_OVERLAP];
|
||||
mQBuffer = new float[q.length + BUFFER_OVERLAP];
|
||||
}
|
||||
|
||||
//Copy last sample to beginning of buffer arrays
|
||||
mIBuffer[0] = mIBuffer[mIBuffer.length - 1];
|
||||
mQBuffer[0] = mQBuffer[mQBuffer.length - 1];
|
||||
|
||||
//Copy new samples to end of buffer arrays
|
||||
System.arraycopy(i, 0, mIBuffer, 1, i.length);
|
||||
System.arraycopy(q, 0, mQBuffer, 1, q.length);
|
||||
|
||||
float[] demodulated = new float[i.length];
|
||||
|
||||
FloatVector currentI, currentQ, previousI, previousQ, demod, demodI, demodQ;
|
||||
|
||||
for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += VECTOR_SPECIES.length())
|
||||
{
|
||||
previousI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer);
|
||||
previousQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer);
|
||||
currentI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer + 1);
|
||||
currentQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer + 1);
|
||||
|
||||
demodI = currentI.mul(previousI).sub(currentQ.mul(previousQ.neg()));
|
||||
demodQ = currentQ.mul(previousI).add(currentI.mul(previousQ.neg()));
|
||||
|
||||
//Replace any zero-values in the I vector by adding Float.MIN_VALUE to side-step divide by zero errors
|
||||
demodI = demodI.add(Float.MIN_VALUE, demodQ.eq(ZERO));
|
||||
|
||||
demod = demodQ.div(demodI).lanewise(VectorOperators.ATAN);
|
||||
demod.intoArray(demodulated, bufferPointer);
|
||||
}
|
||||
|
||||
return demodulated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.fm;
|
||||
|
||||
import io.github.dsheirer.vector.VectorUtilities;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* FM demodulator that uses JDK 17+ SIMD vector intrinsics
|
||||
*/
|
||||
public class VectorFMDemodulator64 implements IDemodulator
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(VectorFMDemodulator64.class);
|
||||
protected static final float ZERO = 0.0f;
|
||||
private static final int BUFFER_OVERLAP = 1;
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||
|
||||
//Initialize buffers to be non-null and resize once the first buffer arrives
|
||||
private float[] mIBuffer = new float[1];
|
||||
private float[] mQBuffer = new float[1];
|
||||
|
||||
public VectorFMDemodulator64()
|
||||
{
|
||||
}
|
||||
|
||||
@Override public float[] demodulate(float[] i, float[] q)
|
||||
{
|
||||
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||
|
||||
if(mIBuffer.length != (i.length + BUFFER_OVERLAP))
|
||||
{
|
||||
mIBuffer = new float[i.length + BUFFER_OVERLAP];
|
||||
mQBuffer = new float[q.length + BUFFER_OVERLAP];
|
||||
}
|
||||
|
||||
//Copy last sample to beginning of buffer arrays
|
||||
mIBuffer[0] = mIBuffer[mIBuffer.length - 1];
|
||||
mQBuffer[0] = mQBuffer[mQBuffer.length - 1];
|
||||
|
||||
//Copy new samples to end of buffer arrays
|
||||
System.arraycopy(i, 0, mIBuffer, 1, i.length);
|
||||
System.arraycopy(q, 0, mQBuffer, 1, q.length);
|
||||
|
||||
float[] demodulated = new float[i.length];
|
||||
|
||||
FloatVector currentI, currentQ, previousI, previousQ, demod, demodI, demodQ;
|
||||
|
||||
for(int bufferPointer = 0; bufferPointer < mIBuffer.length - 1; bufferPointer += VECTOR_SPECIES.length())
|
||||
{
|
||||
previousI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer);
|
||||
previousQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer);
|
||||
currentI = FloatVector.fromArray(VECTOR_SPECIES, mIBuffer, bufferPointer + 1);
|
||||
currentQ = FloatVector.fromArray(VECTOR_SPECIES, mQBuffer, bufferPointer + 1);
|
||||
|
||||
demodI = currentI.mul(previousI).sub(currentQ.mul(previousQ.neg()));
|
||||
demodQ = currentQ.mul(previousI).add(currentI.mul(previousQ.neg()));
|
||||
|
||||
//Replace any zero-values in the I vector by adding Float.MIN_VALUE to side-step divide by zero errors
|
||||
demodI = demodI.add(Float.MIN_VALUE, demodQ.eq(ZERO));
|
||||
|
||||
demod = demodQ.div(demodI).lanewise(VectorOperators.ATAN);
|
||||
demod.intoArray(demodulated, bufferPointer);
|
||||
}
|
||||
|
||||
return demodulated;
|
||||
}
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 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.fm;
|
||||
|
||||
import io.github.dsheirer.dsp.squelch.PowerSquelch;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import io.github.dsheirer.vector.VectorUtilities;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* FM Demodulator for demodulating complex samples and producing demodulated floating point samples.
|
||||
*
|
||||
* Implements listener of source events to process runtime squelch threshold change request events
|
||||
* which are forwarded to the power squelch control.
|
||||
*/
|
||||
public class VectorSquelchingFMDemodulator extends VectorFMDemodulator
|
||||
implements ISquelchingFmDemodulator, Listener<SourceEvent>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(VectorSquelchingFMDemodulator.class);
|
||||
private static final float TWO = 2.0f;
|
||||
private PowerSquelch mPowerSquelch;
|
||||
private boolean mSquelchChanged = false;
|
||||
|
||||
/**
|
||||
* Creates an FM demodulator instance with a default gain of 1.0.
|
||||
*/
|
||||
public VectorSquelchingFMDemodulator(float alpha, float threshold, int ramp)
|
||||
{
|
||||
mPowerSquelch = new PowerSquelch(alpha, threshold, ramp);
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive notifications of squelch change events from the power squelch.
|
||||
*/
|
||||
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mPowerSquelch.setSourceEventListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Demodulates the complex (I/Q) sample arrays
|
||||
* @param i inphase samples
|
||||
* @param q quadrature samples
|
||||
* @return demodulated real samples
|
||||
*/
|
||||
@Override
|
||||
public float[] demodulate(float[] i, float[] q)
|
||||
{
|
||||
setSquelchChanged(false);
|
||||
|
||||
float[] demodulated = super.demodulate(i, q);
|
||||
float[] magnitude = getMagnitude(i, q);
|
||||
|
||||
for(int x = 0; x < i.length; x++)
|
||||
{
|
||||
mPowerSquelch.process(magnitude[x]);
|
||||
|
||||
if(!(mPowerSquelch.isUnmuted() || mPowerSquelch.isDecay()))
|
||||
{
|
||||
demodulated[x] = ZERO;
|
||||
}
|
||||
|
||||
if(mPowerSquelch.isSquelchChanged())
|
||||
{
|
||||
setSquelchChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
return demodulated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the magnitude of the complex I/Q sample arrays using SIMD vector intrinsics.
|
||||
* @param i sample array
|
||||
* @param q sample array
|
||||
* @return magnitude array
|
||||
*/
|
||||
public static float[] getMagnitude(float[] i, float[] q)
|
||||
{
|
||||
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||
|
||||
float[] magnitudes = new float[i.length];
|
||||
FloatVector iVector, qVector;
|
||||
|
||||
for(int samplePointer = 0; samplePointer < i.length; samplePointer += VECTOR_SPECIES.length())
|
||||
{
|
||||
iVector = FloatVector.fromArray(VECTOR_SPECIES, i, samplePointer);
|
||||
qVector = FloatVector.fromArray(VECTOR_SPECIES, q, samplePointer);
|
||||
iVector.pow(TWO).add(qVector.pow(TWO)).intoArray(magnitudes, samplePointer);
|
||||
}
|
||||
|
||||
return magnitudes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the threshold for squelch control
|
||||
* @param threshold (dB)
|
||||
*/
|
||||
public void setSquelchThreshold(double threshold)
|
||||
{
|
||||
mPowerSquelch.setSquelchThreshold(threshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the squelch state has changed during the processing of buffer(s)
|
||||
*/
|
||||
public boolean isSquelchChanged()
|
||||
{
|
||||
return mSquelchChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or resets the squelch changed flag.
|
||||
*/
|
||||
private void setSquelchChanged(boolean changed)
|
||||
{
|
||||
mSquelchChanged = changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the squelch state is currently muted
|
||||
*/
|
||||
public boolean isMuted()
|
||||
{
|
||||
return mPowerSquelch.isMuted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
//Only forward squelch threshold change request events
|
||||
if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD)
|
||||
{
|
||||
mPowerSquelch.receive(sourceEvent);
|
||||
}
|
||||
else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD)
|
||||
{
|
||||
mPowerSquelch.receive(sourceEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
int iterations = 3_000_000;
|
||||
int sampleSize = 2048;
|
||||
|
||||
Random random = new Random();
|
||||
|
||||
float[] iSamples = new float[sampleSize];
|
||||
float[] qSamples = new float[sampleSize];
|
||||
for(int x = 0; x < iSamples.length; x++)
|
||||
{
|
||||
iSamples[x] = random.nextFloat() * 2.0f - 1.0f;
|
||||
qSamples[x] = random.nextFloat() * 2.0f - 1.0f;
|
||||
}
|
||||
|
||||
|
||||
float POWER_SQUELCH_ALPHA_DECAY = 0.0004f;
|
||||
float POWER_SQUELCH_THRESHOLD_DB = -78.0f;
|
||||
int POWER_SQUELCH_RAMP = 4;
|
||||
|
||||
ScalarFMDemodulator fm = new ScalarFMDemodulator();
|
||||
VectorFMDemodulator vectorFM = new VectorFMDemodulator();
|
||||
ScalarSquelchingFMDemodulator sqLegacy = new ScalarSquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP);
|
||||
VectorSquelchingFMDemodulator vectorSq = new VectorSquelchingFMDemodulator(POWER_SQUELCH_ALPHA_DECAY, POWER_SQUELCH_THRESHOLD_DB, POWER_SQUELCH_RAMP);
|
||||
|
||||
boolean validation = false;
|
||||
|
||||
if(validation)
|
||||
{
|
||||
float[] noSqFmSamples = fm.demodulate(iSamples, qSamples);
|
||||
float[] vectorNoSqFmSamples = vectorFM.demodulate(iSamples, qSamples);
|
||||
float[] sqFmSamples = sqLegacy.demodulate(iSamples, qSamples);
|
||||
float[] vectorSqFmSamples = vectorSq.demodulate(iSamples, qSamples);
|
||||
System.out.println(" NO SQ FM:" + Arrays.toString(noSqFmSamples));
|
||||
System.out.println("VECTOR NO SQ FM:" + Arrays.toString(vectorNoSqFmSamples));
|
||||
System.out.println(" SQ FM:" + Arrays.toString(sqFmSamples));
|
||||
System.out.println(" VECTOR SQ FM:" + Arrays.toString(vectorSqFmSamples));
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("Test Starting ...");
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
double accumulator = 0.0;
|
||||
|
||||
for(int i = 0; i < iterations; i++)
|
||||
{
|
||||
// float[] samples = sqLegacy.demodulate(iSamples, qSamples);
|
||||
float[] samples = vectorSq.demodulate(iSamples, qSamples);
|
||||
accumulator += samples[3];
|
||||
}
|
||||
|
||||
double elapsed = System.currentTimeMillis() - start;
|
||||
|
||||
DecimalFormat df = new DecimalFormat("0.000");
|
||||
System.out.println("Accumulator: " + accumulator);
|
||||
System.out.println("Test Complete. Elapsed Time: " + df.format(elapsed / 1000.0d) + " seconds");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.gain;
|
||||
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Audio gain that normalizes audio amplitude against an objective amplitude value where the audio amplitude values
|
||||
* normally fall in the range of -1.0 to 1.0.
|
||||
*
|
||||
* Removes DC bias on a per audio sample buffer basis.
|
||||
*
|
||||
* This control is designed to work on a per-call basis by monitoring the maximum observed amplitude and adjusting the
|
||||
* gain to normalize the amplitude toward the objective amplitude value. This control is designed to be reset at the
|
||||
* beginning/end of each call segment so that the max observed amplitude is relevant to the current call segment.
|
||||
*
|
||||
* Min and max gains should be sized to constrain the applied value during periods of extended silence at the
|
||||
* beginning of an audio segment or where a single amplitude spike might erroneously affect gain across the segment.
|
||||
*/
|
||||
public class AudioGainAndDcFilter
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(AudioGainAndDcFilter.class);
|
||||
private static final float MAX_AMPLITUDE = 0.95f;
|
||||
private static final float GAIN_LOOP_BANDWIDTH = 0.0015f;
|
||||
private float mMinGain;
|
||||
private float mMaxGain;
|
||||
private float mCurrentGain;
|
||||
private float mObjectiveGain;
|
||||
private float mObjectiveAmplitude;
|
||||
private float mMaxObservedAmplitude;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param minGain to apply to the incoming sample stream.
|
||||
* @param maxGain to apply
|
||||
* @param objectiveAmplitude to achieve by adjusting gain between min and max.
|
||||
*/
|
||||
public AudioGainAndDcFilter(float minGain, float maxGain, float objectiveAmplitude)
|
||||
{
|
||||
mMinGain = minGain;
|
||||
mMaxGain = maxGain;
|
||||
mObjectiveAmplitude = objectiveAmplitude;
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this gain control to prepare for the next audio call/segment.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
mMaxObservedAmplitude = 0.0f;
|
||||
mObjectiveGain = 1.0f;
|
||||
mCurrentGain = 1.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a buffer of audio samples and apply gain.
|
||||
* @param samples to adjust.
|
||||
* @return amplified audio samples
|
||||
*/
|
||||
public float[] process(float[] samples)
|
||||
{
|
||||
float currentAmplitude;
|
||||
float dcAccumulator = 0.0f;
|
||||
|
||||
//Decay the max observed value by 10% each buffer so that an initial spike doesn't carry across all buffers
|
||||
mMaxObservedAmplitude *= 0.9f;
|
||||
|
||||
for(float sample: samples)
|
||||
{
|
||||
dcAccumulator += sample;
|
||||
currentAmplitude = FastMath.min(Math.abs(sample), 2.0f);
|
||||
|
||||
if(currentAmplitude > mMaxObservedAmplitude)
|
||||
{
|
||||
mMaxObservedAmplitude = currentAmplitude;
|
||||
}
|
||||
}
|
||||
|
||||
mObjectiveGain = mObjectiveAmplitude / mMaxObservedAmplitude;
|
||||
|
||||
if(mObjectiveGain > mMaxGain)
|
||||
{
|
||||
mObjectiveGain = mMaxGain;
|
||||
}
|
||||
else if(mObjectiveGain < mMinGain)
|
||||
{
|
||||
mObjectiveGain = mMinGain;
|
||||
}
|
||||
|
||||
float dcOffset = dcAccumulator / samples.length;
|
||||
float gain = mCurrentGain;
|
||||
float objective = mObjectiveGain;
|
||||
float[] processed = new float[samples.length];
|
||||
boolean objectiveAchieved = (gain == objective);
|
||||
float amplified;
|
||||
|
||||
for(int x = 0; x < samples.length; x++)
|
||||
{
|
||||
if(!objectiveAchieved)
|
||||
{
|
||||
gain += ((objective - gain) * GAIN_LOOP_BANDWIDTH);
|
||||
if(Math.abs(objective - gain) < 0.00005f)
|
||||
{
|
||||
gain = objective;
|
||||
objectiveAchieved = true;
|
||||
}
|
||||
}
|
||||
|
||||
amplified = (samples[x] - dcOffset) * gain;
|
||||
amplified = FastMath.min(amplified, MAX_AMPLITUDE);
|
||||
amplified = FastMath.max(amplified, -MAX_AMPLITUDE);
|
||||
processed[x] = amplified;
|
||||
}
|
||||
|
||||
mCurrentGain = gain;
|
||||
mObjectiveGain = objective;
|
||||
return processed;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
* Copyright (C) 2014-2023 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
|
||||
@@ -19,88 +19,80 @@
|
||||
|
||||
package io.github.dsheirer.dsp.gain;
|
||||
|
||||
import io.github.dsheirer.buffer.DoubleCircularBuffer;
|
||||
import io.github.dsheirer.buffer.RealCircularBuffer;
|
||||
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.
|
||||
*/
|
||||
public class AutomaticGainControl
|
||||
{
|
||||
private static final double SAMPLE_RATE = 48000;
|
||||
|
||||
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 double DELAY_TIME_CONSTANT = 0.015;
|
||||
private static final double DELAY_TIME_CONSTANT = 0.01;
|
||||
|
||||
private static final float DELAY_TIME_CONSTANT = 0.015f;
|
||||
/* Peak detector window - time delay in seconds */
|
||||
private static final double WINDOW_TIME_CONSTANT = 0.018;
|
||||
|
||||
private static final float WINDOW_TIME_CONSTANT = 0.018f;
|
||||
/* 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 -
|
||||
FastMath.exp(-1.0 / SAMPLE_RATE * ATTACK_RISE_TIME_CONSTANT);
|
||||
|
||||
private static final double ATTACK_FALL_ALPHA = 1.0 -
|
||||
FastMath.exp(-1.0 / SAMPLE_RATE * ATTACK_FALL_TIME_CONSTANT);
|
||||
|
||||
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_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) */
|
||||
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;
|
||||
|
||||
private static final double DECAY_RISE_ALPHA = 1.0 -
|
||||
FastMath.exp(-1.0 / (SAMPLE_RATE * DECAY * .001 * DECAY_RISEFALL_RATIO));
|
||||
|
||||
private static final double DECAY_FALL_ALPHA = 1.0 -
|
||||
FastMath.exp(-1.0 / (SAMPLE_RATE * DECAY * .001));
|
||||
|
||||
/* Hang timer release decay time constant in seconds */
|
||||
@SuppressWarnings("unused")
|
||||
private static final double RELEASE_TIME_CONSTANT = 0.05;
|
||||
|
||||
/* Specifies the AGC Knee in dB if AGC is active (nominal range -160 to 0 dB) */
|
||||
private static final double THRESHOLD = -100;
|
||||
|
||||
/* Limit output to about 3db of maximum */
|
||||
private static final double AGC_OUT_SCALE = 0.7;
|
||||
|
||||
private static final float DECAY = 200.0f;
|
||||
/* Ratio between rise and fall times of decay time constants - adjust for best action with SSB */
|
||||
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));
|
||||
/* 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;
|
||||
|
||||
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 double MANUAL_GAIN = 0.0;
|
||||
|
||||
private static final double MANUAL_AGC_GAIN = MAX_MANUAL_AMPLITUDE *
|
||||
FastMath.pow(10.0, MANUAL_GAIN / 20.0);
|
||||
|
||||
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 double SLOPE_FACTOR = 2.0;
|
||||
|
||||
private static final double KNEE = THRESHOLD / 20.0;
|
||||
|
||||
private static final double GAIN_SLOPE = SLOPE_FACTOR / 100.0;
|
||||
|
||||
private static final double FIXED_GAIN = AGC_OUT_SCALE *
|
||||
FastMath.pow(10.0, KNEE * (GAIN_SLOPE - 1.0));
|
||||
|
||||
private static final float SLOPE_FACTOR = 2.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 double MIN_CONSTANT = 3.2767E-4;
|
||||
|
||||
private static final float MIN_CONSTANT = 3.2767E-4f;
|
||||
private boolean mAGCEnabled = true;
|
||||
private float mPeakMagnitude = -5.0f;
|
||||
private float mAttackAverage = 0.0f;
|
||||
private float mDecayAverage = 0.0f;
|
||||
private FloatCircularBuffer mDelayBuffer = new FloatCircularBuffer((int)(SAMPLE_RATE * DELAY_TIME_CONSTANT));
|
||||
private FloatCircularBuffer mMagnitudeBuffer = new FloatCircularBuffer((int)(SAMPLE_RATE * WINDOW_TIME_CONSTANT), mPeakMagnitude);
|
||||
private float mMaxGain = 0f;
|
||||
private float mMinGain = 100000f;
|
||||
|
||||
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));
|
||||
|
||||
/**
|
||||
* Construct an instance
|
||||
*/
|
||||
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 = -8.0f;
|
||||
mAttackAverage = -8.0f;
|
||||
mDecayAverage = -8.0f;
|
||||
mDelayBuffer.reset(0.0f);
|
||||
mMagnitudeBuffer.reset(mPeakMagnitude);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,15 +122,14 @@ public class AutomaticGainControl
|
||||
*/
|
||||
public float process(float currentSample)
|
||||
{
|
||||
float delayedSample = mDelayBuffer.putAndGet(currentSample);
|
||||
float delayedSample = mDelayBuffer.get(currentSample);
|
||||
|
||||
double gain = MANUAL_AGC_GAIN;
|
||||
float gain = MANUAL_AGC_GAIN;
|
||||
|
||||
if(mAGCEnabled)
|
||||
{
|
||||
double currentMagnitude = FastMath.log10(FastMath.abs(currentSample) + MIN_CONSTANT) - FastMath.log10(MAX_AMPLITUDE);
|
||||
|
||||
double delayedMagnitude = mMagnitudeBuffer.get(currentMagnitude);
|
||||
float currentMagnitude = (float)(FastMath.log10(FastMath.abs(currentSample) + MIN_CONSTANT) - FastMath.log10(MAX_AMPLITUDE));
|
||||
float delayedMagnitude = mMagnitudeBuffer.get(currentMagnitude);
|
||||
|
||||
if(currentMagnitude > mPeakMagnitude)
|
||||
{
|
||||
@@ -148,29 +139,30 @@ public class AutomaticGainControl
|
||||
else if(delayedMagnitude == mPeakMagnitude)
|
||||
{
|
||||
/* If delayed magnitude is the current peak, then find a new peak */
|
||||
mPeakMagnitude = mMagnitudeBuffer.max();
|
||||
mPeakMagnitude = mMagnitudeBuffer.max(-3.4845634f);
|
||||
}
|
||||
|
||||
/* Exponential decay mode */
|
||||
if(mPeakMagnitude > mAttackAverage)
|
||||
{
|
||||
mAttackAverage = ((1.0 - ATTACK_RISE_ALPHA) * mAttackAverage) + (ATTACK_RISE_ALPHA * mPeakMagnitude);
|
||||
mAttackAverage = ((1.0f - ATTACK_RISE_ALPHA) * mAttackAverage) + (ATTACK_RISE_ALPHA * mPeakMagnitude);
|
||||
}
|
||||
else
|
||||
{
|
||||
mAttackAverage = ((1.0 - ATTACK_FALL_ALPHA) * mAttackAverage) + (ATTACK_FALL_ALPHA * mPeakMagnitude);
|
||||
mAttackAverage = ((1.0f - ATTACK_FALL_ALPHA) * mAttackAverage) + (ATTACK_FALL_ALPHA * mPeakMagnitude);
|
||||
}
|
||||
|
||||
if(mPeakMagnitude > mDecayAverage)
|
||||
{
|
||||
mDecayAverage = ((1.0 - DECAY_RISE_ALPHA) * mDecayAverage) + (DECAY_RISE_ALPHA * mPeakMagnitude);
|
||||
mDecayAverage = ((1.0f - DECAY_RISE_ALPHA) * mDecayAverage) + (DECAY_RISE_ALPHA * mPeakMagnitude);
|
||||
}
|
||||
else
|
||||
{
|
||||
mDecayAverage = ((1.0 - DECAY_FALL_ALPHA) * mDecayAverage) + (DECAY_RISE_ALPHA * mPeakMagnitude);
|
||||
mDecayAverage = ((1.0f - DECAY_FALL_ALPHA) * mDecayAverage) + (DECAY_RISE_ALPHA * mPeakMagnitude);
|
||||
}
|
||||
|
||||
double magnitude = FastMath.max(mAttackAverage, mDecayAverage);
|
||||
|
||||
float magnitude = FastMath.max(mAttackAverage, mDecayAverage);
|
||||
|
||||
if(magnitude < KNEE)
|
||||
{
|
||||
@@ -178,11 +170,24 @@ public class AutomaticGainControl
|
||||
}
|
||||
else
|
||||
{
|
||||
gain = AGC_OUT_SCALE * FastMath.pow(10.0, magnitude * (GAIN_SLOPE - 1.0));
|
||||
gain = AGC_OUT_SCALE * (float)FastMath.pow(10.0, magnitude * (GAIN_SLOPE - 1.0));
|
||||
}
|
||||
|
||||
System.out.println("Current Mag: " + currentMagnitude + " Delay Mag: " + delayedMagnitude +
|
||||
" Peak:" + mPeakMagnitude + " Gain:" + gain +
|
||||
" Attack Avg:" + mAttackAverage + " Decay Avg:" + mDecayAverage);
|
||||
}
|
||||
|
||||
return (float)(delayedSample * gain);
|
||||
if(gain < mMinGain)
|
||||
{
|
||||
mMinGain = gain;
|
||||
}
|
||||
if(gain > mMaxGain)
|
||||
{
|
||||
mMaxGain = gain;
|
||||
}
|
||||
|
||||
return delayedSample * gain;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,4 +207,5 @@ public class AutomaticGainControl
|
||||
{
|
||||
return mAGCEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.magnitude;
|
||||
|
||||
/**
|
||||
* Interface for implementations that calculate magnitude
|
||||
*/
|
||||
public interface IMagnitudeCalculator
|
||||
{
|
||||
float[] calculate(float[] i, float[] q);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.magnitude;
|
||||
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationManager;
|
||||
import io.github.dsheirer.vector.calibrate.CalibrationType;
|
||||
import io.github.dsheirer.vector.calibrate.Implementation;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Factory for creating magnitude implementations
|
||||
*/
|
||||
public class MagnitudeFactory
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(MagnitudeFactory.class);
|
||||
|
||||
/**
|
||||
* Creates a magnitude implementation using calibration data to select the optimal
|
||||
* implementation from scalar and vector options.
|
||||
* @return demodulator instance
|
||||
*/
|
||||
public static IMagnitudeCalculator getMagnitudeCalculator()
|
||||
{
|
||||
Implementation implementation = CalibrationManager.getInstance().getImplementation(CalibrationType.MAGNITUDE);
|
||||
|
||||
switch(implementation)
|
||||
{
|
||||
case VECTOR_SIMD_64:
|
||||
return new VectorMagnitudeCalculator64();
|
||||
case VECTOR_SIMD_128:
|
||||
return new VectorMagnitudeCalculator128();
|
||||
case VECTOR_SIMD_256:
|
||||
return new VectorMagnitudeCalculator256();
|
||||
case VECTOR_SIMD_512:
|
||||
return new VectorMagnitudeCalculator512();
|
||||
case SCALAR:
|
||||
default:
|
||||
return new ScalarMagnitudeCalculator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.magnitude;
|
||||
|
||||
/**
|
||||
* Scalar implementation of magnitude
|
||||
*/
|
||||
public class ScalarMagnitudeCalculator implements IMagnitudeCalculator
|
||||
{
|
||||
@Override
|
||||
public float[] calculate(float[] i, float[] q)
|
||||
{
|
||||
float[] magnitude = new float[i.length];
|
||||
|
||||
for(int x = 0; x < i.length; x++)
|
||||
{
|
||||
magnitude[x] = ((i[x] * i[x]) + (q[x] * q[x]));
|
||||
}
|
||||
|
||||
return magnitude;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.magnitude;
|
||||
|
||||
import io.github.dsheirer.vector.VectorUtilities;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector implementation of magnitude calculation.
|
||||
*/
|
||||
public class VectorMagnitudeCalculator128 implements IMagnitudeCalculator
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_128;
|
||||
|
||||
@Override
|
||||
public float[] calculate(float[] i, float[] q)
|
||||
{
|
||||
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||
float[] magnitude = new float[i.length];
|
||||
|
||||
FloatVector iVector, qVector, result;
|
||||
for(int x = 0; x < i.length; x += VECTOR_SPECIES.length())
|
||||
{
|
||||
iVector = FloatVector.fromArray(VECTOR_SPECIES, i, x);
|
||||
qVector = FloatVector.fromArray(VECTOR_SPECIES, q, x);
|
||||
result = iVector.mul(iVector).add(qVector.mul(qVector));
|
||||
result.intoArray(magnitude, x);
|
||||
}
|
||||
|
||||
return magnitude;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.magnitude;
|
||||
|
||||
import io.github.dsheirer.vector.VectorUtilities;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector implementation of magnitude calculation.
|
||||
*/
|
||||
public class VectorMagnitudeCalculator256 implements IMagnitudeCalculator
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_256;
|
||||
|
||||
@Override
|
||||
public float[] calculate(float[] i, float[] q)
|
||||
{
|
||||
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||
float[] magnitude = new float[i.length];
|
||||
|
||||
FloatVector iVector, qVector, result;
|
||||
for(int x = 0; x < i.length; x += VECTOR_SPECIES.length())
|
||||
{
|
||||
iVector = FloatVector.fromArray(VECTOR_SPECIES, i, x);
|
||||
qVector = FloatVector.fromArray(VECTOR_SPECIES, q, x);
|
||||
result = iVector.mul(iVector).add(qVector.mul(qVector));
|
||||
result.intoArray(magnitude, x);
|
||||
}
|
||||
|
||||
return magnitude;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.magnitude;
|
||||
|
||||
import io.github.dsheirer.vector.VectorUtilities;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector implementation of magnitude calculation.
|
||||
*/
|
||||
public class VectorMagnitudeCalculator512 implements IMagnitudeCalculator
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_512;
|
||||
|
||||
@Override
|
||||
public float[] calculate(float[] i, float[] q)
|
||||
{
|
||||
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||
float[] magnitude = new float[i.length];
|
||||
|
||||
FloatVector iVector, qVector, result;
|
||||
for(int x = 0; x < i.length; x += VECTOR_SPECIES.length())
|
||||
{
|
||||
iVector = FloatVector.fromArray(VECTOR_SPECIES, i, x);
|
||||
qVector = FloatVector.fromArray(VECTOR_SPECIES, q, x);
|
||||
result = iVector.mul(iVector).add(qVector.mul(qVector));
|
||||
result.intoArray(magnitude, x);
|
||||
}
|
||||
|
||||
return magnitude;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.magnitude;
|
||||
|
||||
import io.github.dsheirer.vector.VectorUtilities;
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* Vector implementation of magnitude calculation.
|
||||
*/
|
||||
public class VectorMagnitudeCalculator64 implements IMagnitudeCalculator
|
||||
{
|
||||
private static final VectorSpecies<Float> VECTOR_SPECIES = FloatVector.SPECIES_64;
|
||||
|
||||
@Override
|
||||
public float[] calculate(float[] i, float[] q)
|
||||
{
|
||||
VectorUtilities.checkComplexArrayLength(i, q, VECTOR_SPECIES);
|
||||
float[] magnitude = new float[i.length];
|
||||
|
||||
FloatVector iVector, qVector, result;
|
||||
for(int x = 0; x < i.length; x += VECTOR_SPECIES.length())
|
||||
{
|
||||
iVector = FloatVector.fromArray(VECTOR_SPECIES, i, x);
|
||||
qVector = FloatVector.fromArray(VECTOR_SPECIES, q, x);
|
||||
result = iVector.mul(iVector).add(qVector.mul(qVector));
|
||||
result.intoArray(magnitude, x);
|
||||
}
|
||||
|
||||
return magnitude;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.squelch;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.iir.SinglePoleIirFilter;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import java.time.Duration;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Adaptive squelch control that optionally tracks the noise floor and auto-adjusts squelch threshold.
|
||||
*
|
||||
* Initially modeled on: https://github.com/gnuradio/gnuradio/blob/master/gr-analog/lib/simple_squelch_cc_impl.cc
|
||||
*/
|
||||
public class AdaptiveSquelch implements Listener<SourceEvent>
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(AdaptiveSquelch.class);
|
||||
private static final long AUTO_ADJUST_PERIOD_MS = Duration.ofSeconds(5).toMillis();
|
||||
private SinglePoleIirFilter mFilter;
|
||||
private float mPower = 0.0f;
|
||||
private boolean mSquelch;
|
||||
private float mSquelchThreshold;
|
||||
private Float mNoiseFloor;
|
||||
private float mSquelchBuffer;
|
||||
private boolean mSquelchChanged = false;
|
||||
private int mPowerLevelBroadcastCount = 0;
|
||||
private int mPowerLevelBroadcastThreshold;
|
||||
private boolean mAutoTrackNoiseFloor;
|
||||
private long mLastAutoAdjustTimestamp;
|
||||
private Listener<SourceEvent> mSourceEventListener;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
*
|
||||
* Testing against a 12.5 kHz analog FM modulated signal, the following parameters provided a
|
||||
* good responsiveness. A threshold of -80.0 dB seemed to trigger significant flapping during un-squelching.
|
||||
* - alpha: 0.0001
|
||||
* - threshold: 78.0 dB
|
||||
*
|
||||
* @param alpha decay value of the single pole IIR filter in range: 0.0 - 1.0. The smaller the alpha value,
|
||||
* the slower the squelch response.
|
||||
* @param squelchThreshold in decibels. Signal power must exceed this threshold value for unsquelch.
|
||||
* @param autoTrack the noise floor when squelched and periodically readjust the squelch threshold.
|
||||
*/
|
||||
public AdaptiveSquelch(float alpha, float squelchThreshold, boolean autoTrack)
|
||||
{
|
||||
mFilter = new SinglePoleIirFilter(alpha);
|
||||
setSquelchThreshold(squelchThreshold);
|
||||
mAutoTrackNoiseFloor = autoTrack;
|
||||
mLastAutoAdjustTimestamp = System.currentTimeMillis();
|
||||
mPowerLevelBroadcastThreshold = 25000; //Based on a default sample rate of 50 kHz, so 2x/second
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sample rate to effect the frequency of power level notifications where the notifications are
|
||||
* sent twice a second.
|
||||
* @param sampleRate in hertz
|
||||
*/
|
||||
public void setSampleRate(int sampleRate)
|
||||
{
|
||||
mPowerLevelBroadcastThreshold = sampleRate / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Squelch threshold value
|
||||
* @return value in decibels
|
||||
*/
|
||||
public float getSquelchThreshold()
|
||||
{
|
||||
return toDb(mSquelchThreshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the value as decibels
|
||||
* @param value to format
|
||||
* @return formatted value.
|
||||
*/
|
||||
private float toDb(float value)
|
||||
{
|
||||
return 10.0f * (float)FastMath.log10(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from a decibel value
|
||||
* @param valueDb in decibels
|
||||
* @return not decibels
|
||||
*/
|
||||
private float fromDb(float valueDb)
|
||||
{
|
||||
return (float)FastMath.pow(10.0, valueDb / 10.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the squelch auto-tracking feature where the squelch threshold is auto-adjusted based on the
|
||||
* observed channel power when the signal is not present (ie squelch state).
|
||||
* @param autoTrack true to turn on auto-track
|
||||
*/
|
||||
public void setSquelchAutoTrack(boolean autoTrack)
|
||||
{
|
||||
mAutoTrackNoiseFloor = autoTrack;
|
||||
broadcast(SourceEvent.squelchAutoTrack(getSquelchAutoTrack()));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Current squelch auto-track setting.
|
||||
* @return true if auto-track is enabled.
|
||||
*/
|
||||
public boolean getSquelchAutoTrack()
|
||||
{
|
||||
return mAutoTrackNoiseFloor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the squelch threshold
|
||||
* @param squelchThreshold in decibels
|
||||
*/
|
||||
public void setSquelchThreshold(float squelchThreshold)
|
||||
{
|
||||
float squelchThresholdDb = fromDb(squelchThreshold);
|
||||
setSquelchThreshold(squelchThresholdDb, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the squelch threshold. This method is synchronized to coordinate changes requested by the user/UI thread
|
||||
* and auto-track changes made by the stream processing thread.
|
||||
*
|
||||
* @param squelchThreshold value
|
||||
* @param resetNoiseFloor to reset the noise floor and squelch threshold
|
||||
*/
|
||||
private synchronized void setSquelchThreshold(float squelchThreshold, boolean resetNoiseFloor)
|
||||
{
|
||||
mSquelchThreshold = squelchThreshold;
|
||||
|
||||
if(resetNoiseFloor)
|
||||
{
|
||||
//Set noise floor to null so that both the noise floor and the squelch buffer get updated after first power reading
|
||||
mNoiseFloor = null;
|
||||
}
|
||||
|
||||
broadcast(SourceEvent.squelchThreshold(null, getSquelchThreshold()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the squelch state
|
||||
* @param squelch true to squelch and false to unsquelch
|
||||
*/
|
||||
private void setSquelch(boolean squelch)
|
||||
{
|
||||
mSquelch = squelch;
|
||||
mSquelchChanged = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a complex IQ sample and changes squelch state when the signal power is above or below the
|
||||
* threshold value.
|
||||
* @param inphase complex sample component
|
||||
* @param quadrature complex sample component
|
||||
*/
|
||||
public void process(float inphase, float quadrature)
|
||||
{
|
||||
process(inphase * inphase + quadrature * quadrature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a complex IQ sample and changes squelch state when the signal power is above or below the
|
||||
* threshold value.
|
||||
* @param magnitude of a complex sample (inphase * inphase + quadrature * quadrature)
|
||||
*/
|
||||
public void process(float magnitude)
|
||||
{
|
||||
mPower = mFilter.filter(magnitude);
|
||||
|
||||
if(mSquelch && mPower >= mSquelchThreshold)
|
||||
{
|
||||
setSquelch(false);
|
||||
}
|
||||
else if(!mSquelch && mPower < mSquelchThreshold)
|
||||
{
|
||||
setSquelch(true);
|
||||
}
|
||||
|
||||
mPowerLevelBroadcastCount++;
|
||||
if(mPowerLevelBroadcastCount % mPowerLevelBroadcastThreshold == 0)
|
||||
{
|
||||
mPowerLevelBroadcastCount = 0;
|
||||
broadcast(SourceEvent.channelPowerLevel(null, 10.0 * Math.log10(mPower)));
|
||||
|
||||
if(mSquelch)
|
||||
{
|
||||
adjustSquelchThreshold();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* With the user specified squelch threshold, establishes a squelch buffer (delta) from the actual noise floor and
|
||||
* then automatically updates the squelch threshold to be the current noise floor plus this threshold value.
|
||||
*
|
||||
* Note: this method should only be invoked from a squelch (true) state.
|
||||
*/
|
||||
private void adjustSquelchThreshold()
|
||||
{
|
||||
if(mAutoTrackNoiseFloor)
|
||||
{
|
||||
if(mNoiseFloor == null)
|
||||
{
|
||||
//We set the squelch buffer just once, after the squelch threshold is set and noise floor is set to null
|
||||
mSquelchBuffer = mSquelchThreshold - mPower;
|
||||
}
|
||||
|
||||
mNoiseFloor = mPower;
|
||||
|
||||
if((System.currentTimeMillis() - mLastAutoAdjustTimestamp) > AUTO_ADJUST_PERIOD_MS)
|
||||
{
|
||||
setSquelchThreshold(mNoiseFloor + mSquelchBuffer, false);
|
||||
mLastAutoAdjustTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the current state is muted
|
||||
*/
|
||||
public boolean isMuted()
|
||||
{
|
||||
return mSquelch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the current state is unmuted
|
||||
*/
|
||||
public boolean isUnmuted()
|
||||
{
|
||||
return !mSquelch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current power level
|
||||
* @return current power level in dB
|
||||
*/
|
||||
public float getPower()
|
||||
{
|
||||
return (float)(10.0 * Math.log10(mPower));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the squelch state has changed (muted > unmuted, or vice-versa)
|
||||
*/
|
||||
public boolean isSquelchChanged()
|
||||
{
|
||||
return mSquelchChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or resets the squelch changed flag
|
||||
*/
|
||||
public void setSquelchChanged(boolean changed)
|
||||
{
|
||||
mSquelchChanged = changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the listener to receive power level notifications and squelch threshold requests
|
||||
*/
|
||||
public void setSourceEventListener(Listener<SourceEvent> listener)
|
||||
{
|
||||
mSourceEventListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts the source event to an optional register listener
|
||||
*/
|
||||
private void broadcast(SourceEvent event)
|
||||
{
|
||||
if(mSourceEventListener != null)
|
||||
{
|
||||
mSourceEventListener.receive(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary method to receive requests for squelch threshold change
|
||||
*/
|
||||
@Override
|
||||
public void receive(SourceEvent sourceEvent)
|
||||
{
|
||||
switch(sourceEvent.getEvent())
|
||||
{
|
||||
case REQUEST_CHANGE_SQUELCH_THRESHOLD:
|
||||
setSquelchThreshold(sourceEvent.getValue().floatValue());
|
||||
break;
|
||||
case REQUEST_CURRENT_SQUELCH_THRESHOLD:
|
||||
broadcast(SourceEvent.squelchThreshold(null, getSquelchThreshold()));
|
||||
break;
|
||||
case REQUEST_CHANGE_SQUELCH_AUTO_TRACK:
|
||||
setSquelchAutoTrack(sourceEvent.getValue().intValue() == 1 ? true : false);
|
||||
break;
|
||||
case REQUEST_CURRENT_SQUELCH_AUTO_TRACK:
|
||||
broadcast(SourceEvent.squelchAutoTrack(getSquelchAutoTrack()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,4 +16,16 @@ public interface ISquelchConfiguration
|
||||
* @return threshold (dB)
|
||||
*/
|
||||
int getSquelchThreshold();
|
||||
|
||||
/**
|
||||
* Enable or disable the squelch noise floor auto-track feature.
|
||||
* @param autoTrack true to enable.
|
||||
*/
|
||||
void setSquelchAutoTrack(boolean autoTrack);
|
||||
|
||||
/**
|
||||
* Indicates if the squelch noise floor auto-track feature is enabled.
|
||||
* @return true if enabled.
|
||||
*/
|
||||
boolean isSquelchAutoTrack();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,28 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2023 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.squelch;
|
||||
|
||||
import io.github.dsheirer.dsp.filter.iir.SinglePoleIirFilter;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.source.SourceEvent;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -17,8 +37,8 @@ public class PowerSquelch implements Listener<SourceEvent>
|
||||
private static final Logger mLog = LoggerFactory.getLogger(PowerSquelch.class);
|
||||
private State mState = State.MUTE;
|
||||
private SinglePoleIirFilter mFilter;
|
||||
private double mPower = 0.0f;
|
||||
private double mSquelchThreshold;
|
||||
private float mPower = 0.0f;
|
||||
private float mSquelchThreshold;
|
||||
private int mRampThreshold;
|
||||
private int mRampCount;
|
||||
private boolean mSquelchChanged = false;
|
||||
@@ -42,7 +62,7 @@ public class PowerSquelch implements Listener<SourceEvent>
|
||||
* @param ramp count of samples before transition between mute and unmute. Setting this value to zero
|
||||
* causes immediate mute and unmute. Set to higher count to prevent mute/unmute flapping.
|
||||
*/
|
||||
public PowerSquelch(float alpha, double squelchThreshold, int ramp)
|
||||
public PowerSquelch(float alpha, float squelchThreshold, int ramp)
|
||||
{
|
||||
mFilter = new SinglePoleIirFilter(alpha);
|
||||
setSquelchThreshold(squelchThreshold);
|
||||
@@ -64,9 +84,9 @@ public class PowerSquelch implements Listener<SourceEvent>
|
||||
* Squelch threshold value
|
||||
* @return value in decibels
|
||||
*/
|
||||
public double getSquelchThreshold()
|
||||
public float getSquelchThreshold()
|
||||
{
|
||||
return 10.0 * Math.log10(mSquelchThreshold);
|
||||
return 10.0f * (float)FastMath.log10(mSquelchThreshold);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +95,7 @@ public class PowerSquelch implements Listener<SourceEvent>
|
||||
*/
|
||||
public void setSquelchThreshold(double squelchThreshold)
|
||||
{
|
||||
mSquelchThreshold = Math.pow(10.0, squelchThreshold / 10.0);
|
||||
mSquelchThreshold = (float) FastMath.pow(10.0, squelchThreshold / 10.0);
|
||||
broadcast(SourceEvent.squelchThreshold(null, squelchThreshold));
|
||||
}
|
||||
|
||||
@@ -268,7 +288,7 @@ public class PowerSquelch implements Listener<SourceEvent>
|
||||
{
|
||||
if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CHANGE_SQUELCH_THRESHOLD)
|
||||
{
|
||||
setSquelchThreshold(sourceEvent.getValue().doubleValue());
|
||||
setSquelchThreshold(sourceEvent.getValue().floatValue());
|
||||
}
|
||||
else if(sourceEvent.getEvent() == SourceEvent.Event.REQUEST_CURRENT_SQUELCH_THRESHOLD)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user