mirror of
https://github.com/SDRSharpR/SDRSharper.git
synced 2026-01-03 23:19:59 +01:00
169 lines
3.6 KiB
C#
169 lines
3.6 KiB
C#
using System;
|
|
|
|
namespace SDRSharp.Radio
|
|
{
|
|
public sealed class FmDetector
|
|
{
|
|
private const float NarrowAFGain = 0.5f;
|
|
|
|
private const float FMGain = 1E-05f;
|
|
|
|
private const float TimeConst = 1E-06f;
|
|
|
|
private const int MinHissFrequency = 4000;
|
|
|
|
private const int MaxHissFrequency = 6000;
|
|
|
|
private const int HissFilterOrder = 20;
|
|
|
|
private const float HissFactor = 2E-05f;
|
|
|
|
private unsafe readonly DcRemover* _dcRemoverPtr;
|
|
|
|
private unsafe readonly UnsafeBuffer _dcRemoverBuffer = UnsafeBuffer.Create(sizeof(DcRemover));
|
|
|
|
private unsafe float* _hissPtr;
|
|
|
|
private UnsafeBuffer _hissBuffer;
|
|
|
|
private FirFilter _hissFilter;
|
|
|
|
private Complex _iqState;
|
|
|
|
private float _noiseLevel;
|
|
|
|
private double _sampleRate;
|
|
|
|
private float _noiseAveragingRatio;
|
|
|
|
private int _squelchThreshold;
|
|
|
|
private bool _isSquelchOpen;
|
|
|
|
private float _noiseThreshold;
|
|
|
|
private FmMode _mode;
|
|
|
|
private Complex _f = default(Complex);
|
|
|
|
private Complex _tmp = default(Complex);
|
|
|
|
public unsafe float Offset => this._dcRemoverPtr->Offset;
|
|
|
|
public unsafe double SampleRate
|
|
{
|
|
get
|
|
{
|
|
return this._sampleRate;
|
|
}
|
|
set
|
|
{
|
|
if (value != this._sampleRate)
|
|
{
|
|
this._sampleRate = value;
|
|
this._noiseAveragingRatio = (float)(30.0 / this._sampleRate);
|
|
float[] coefficients = FilterBuilder.MakeBandPassKernel(this._sampleRate, 20, 4000.0, 6000.0, WindowType.BlackmanHarris4);
|
|
if (this._hissFilter != null)
|
|
{
|
|
this._hissFilter.Dispose();
|
|
}
|
|
this._hissFilter = new FirFilter(coefficients, 1);
|
|
this._dcRemoverPtr->Reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
public int SquelchThreshold
|
|
{
|
|
get
|
|
{
|
|
return this._squelchThreshold;
|
|
}
|
|
set
|
|
{
|
|
if (this._squelchThreshold != value)
|
|
{
|
|
this._squelchThreshold = value;
|
|
this._noiseThreshold = (float)Math.Log10(2.0 - (double)this._squelchThreshold / 100.0) * 2E-05f;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsSquelchOpen => this._isSquelchOpen;
|
|
|
|
public FmMode Mode
|
|
{
|
|
get
|
|
{
|
|
return this._mode;
|
|
}
|
|
set
|
|
{
|
|
this._mode = value;
|
|
}
|
|
}
|
|
|
|
public unsafe FmDetector()
|
|
{
|
|
this._dcRemoverPtr = (DcRemover*)(void*)this._dcRemoverBuffer;
|
|
this._dcRemoverPtr->Init(1E-06f);
|
|
}
|
|
|
|
public unsafe void Demodulate(Complex* iq, float* audio, int length)
|
|
{
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
Complex.Conjugate(ref this._tmp, this._iqState);
|
|
Complex.Mul(ref this._f, iq[i], this._tmp);
|
|
float num = this._f.Modulus();
|
|
if (num > 0f)
|
|
{
|
|
Complex.Div(ref this._f, num);
|
|
}
|
|
float num2 = this._f.Argument();
|
|
audio[i] = num2 * 1E-05f;
|
|
this._iqState = iq[i];
|
|
}
|
|
if (this._mode == FmMode.Narrow)
|
|
{
|
|
this.ProcessSquelch(audio, length);
|
|
for (int j = 0; j < length; j++)
|
|
{
|
|
audio[j] *= 0.5f;
|
|
}
|
|
}
|
|
}
|
|
|
|
private unsafe void ProcessSquelch(float* audio, int length)
|
|
{
|
|
if (this._squelchThreshold > 0)
|
|
{
|
|
if (this._hissBuffer == null || this._hissBuffer.Length != length)
|
|
{
|
|
this._hissBuffer = UnsafeBuffer.Create(length, 4);
|
|
this._hissPtr = (float*)(void*)this._hissBuffer;
|
|
}
|
|
Utils.Memcpy(this._hissPtr, audio, length * 4);
|
|
this._hissFilter.Process(this._hissPtr, length);
|
|
for (int i = 0; i < this._hissBuffer.Length; i++)
|
|
{
|
|
float num = (1f - this._noiseAveragingRatio) * this._noiseLevel + this._noiseAveragingRatio * Math.Abs(this._hissPtr[i]);
|
|
if (!float.IsNaN(num))
|
|
{
|
|
this._noiseLevel = num;
|
|
}
|
|
if (this._noiseLevel > this._noiseThreshold)
|
|
{
|
|
audio[i] = 0f;
|
|
}
|
|
}
|
|
this._isSquelchOpen = (this._noiseLevel < this._noiseThreshold);
|
|
}
|
|
else
|
|
{
|
|
this._isSquelchOpen = true;
|
|
}
|
|
}
|
|
}
|
|
}
|