mirror of
https://github.com/SDRSharpR/SDRSharper.git
synced 2025-12-06 04:12:02 +01:00
202 lines
4.5 KiB
C#
202 lines
4.5 KiB
C#
using System;
|
|
|
|
namespace SDRSharp.Radio
|
|
{
|
|
public sealed class SamDetector
|
|
{
|
|
private const float ZETA = 0.707f;
|
|
|
|
private float TWOPI = 6.28318548f;
|
|
|
|
private float _sampleRate = 48000f;
|
|
|
|
private float _norm = 1f;
|
|
|
|
private float _fDefault;
|
|
|
|
private float _bw = 400f;
|
|
|
|
private float _range = 500f;
|
|
|
|
private float _lockTreshold;
|
|
|
|
private float _lockTime;
|
|
|
|
private float _phase;
|
|
|
|
private float _dcReal;
|
|
|
|
private float _dcImag;
|
|
|
|
private float _freqN;
|
|
|
|
private float _bwN;
|
|
|
|
private float _fLowN;
|
|
|
|
private float _fhighN;
|
|
|
|
private float _alpha;
|
|
|
|
private float _beta;
|
|
|
|
private float _errorAvg;
|
|
|
|
private float _lockAlpha;
|
|
|
|
private float _lockOneMinAlpha;
|
|
|
|
private CuteFir _cuteFir = new CuteFir();
|
|
|
|
private UnsafeBuffer _cpxBuf;
|
|
|
|
private unsafe Complex* _cpxPtr;
|
|
|
|
public float SampleRate
|
|
{
|
|
get
|
|
{
|
|
return this._sampleRate;
|
|
}
|
|
set
|
|
{
|
|
this._sampleRate = value;
|
|
this._norm = this.TWOPI / this._sampleRate;
|
|
this._cuteFir.InitLPFilter(0, 1.0, 40.0, 4500f, 5500f, this._sampleRate);
|
|
this._cuteFir.GenerateHBFilter(5000f);
|
|
}
|
|
}
|
|
|
|
public float Frequency
|
|
{
|
|
get
|
|
{
|
|
return (0f - this._freqN) / this._norm;
|
|
}
|
|
set
|
|
{
|
|
this._fDefault = value;
|
|
this._freqN = this._fDefault * this._norm;
|
|
}
|
|
}
|
|
|
|
public float Range
|
|
{
|
|
get
|
|
{
|
|
return this._range;
|
|
}
|
|
set
|
|
{
|
|
this._range = value;
|
|
this._fLowN = (this._fDefault - this._range) * this._norm;
|
|
this._fhighN = (this._fDefault + this._range) * this._norm;
|
|
}
|
|
}
|
|
|
|
public bool IsLocked => this._errorAvg < this._lockTreshold;
|
|
|
|
public float LockTreshold
|
|
{
|
|
set
|
|
{
|
|
this._lockTreshold = value;
|
|
}
|
|
}
|
|
|
|
public float LockTime
|
|
{
|
|
set
|
|
{
|
|
this._lockTime = value;
|
|
}
|
|
}
|
|
|
|
public float BandWidth
|
|
{
|
|
get
|
|
{
|
|
return this._bw;
|
|
}
|
|
set
|
|
{
|
|
this._bw = value;
|
|
this._bwN = this._bw * this._norm;
|
|
this._alpha = 0.3f * this._bwN;
|
|
this._beta = this._alpha * this._alpha * 0.25f;
|
|
this._lockAlpha = (float)(1.0 - Math.Exp(-1.0 / (double)(this._sampleRate * this._lockTime)));
|
|
this._lockOneMinAlpha = 1f - this._lockAlpha;
|
|
}
|
|
}
|
|
|
|
public unsafe void Demodulate(Complex* iq, float* audio, int length, bool stereo = false, bool filter = false)
|
|
{
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
float num = (float)Math.Cos((double)this._phase);
|
|
float num2 = (float)Math.Sin((double)this._phase);
|
|
float num3 = num * iq[i].Real + num2 * iq[i].Imag;
|
|
float num4 = (0f - num2) * iq[i].Real + num * iq[i].Imag;
|
|
float num5 = (float)Math.Atan2((double)num4, (double)num3);
|
|
this._freqN += this._beta * num5;
|
|
if (this._freqN < this._fLowN)
|
|
{
|
|
this.Frequency = this._fLowN;
|
|
}
|
|
else if (this._freqN > this._fhighN)
|
|
{
|
|
this.Frequency = this._fhighN;
|
|
}
|
|
this._phase += this._freqN + this._alpha * num5;
|
|
while (this._phase >= this.TWOPI)
|
|
{
|
|
this._phase -= this.TWOPI;
|
|
}
|
|
while (this._phase < 0f)
|
|
{
|
|
this._phase += this.TWOPI;
|
|
}
|
|
this._dcReal = 0.9999f * this._dcReal + 0.0001f * num3;
|
|
this._errorAvg = this._lockOneMinAlpha * this._errorAvg + this._lockAlpha * num5 * num5;
|
|
audio[i] = (num3 - this._dcReal) * 0.002f;
|
|
}
|
|
}
|
|
|
|
public unsafe void Demodulate(Complex* iq, float* rPtr, float* lPtr, int length)
|
|
{
|
|
if (this._cpxBuf == null || this._cpxBuf.Length != length)
|
|
{
|
|
if (this._cpxBuf != null)
|
|
{
|
|
this._cpxBuf.Dispose();
|
|
}
|
|
this._cpxBuf = UnsafeBuffer.Create(length, sizeof(Complex));
|
|
this._cpxPtr = (Complex*)(void*)this._cpxBuf;
|
|
}
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
float num = (float)Math.Cos((double)this._phase);
|
|
float num2 = (float)Math.Sin((double)this._phase);
|
|
float num3 = num * iq[i].Real + num2 * iq[i].Imag;
|
|
float num4 = (0f - num2) * iq[i].Real + num * iq[i].Imag;
|
|
float num5 = (float)Math.Atan2((double)num4, (double)num3);
|
|
this._freqN += this._beta * num5;
|
|
this._freqN = Math.Max(this._fLowN, Math.Min(this._fhighN, this._freqN));
|
|
this._phase += this._freqN + this._alpha * num5;
|
|
this._phase %= this.TWOPI;
|
|
this._dcReal = 0.9999f * this._dcReal + 0.0001f * num3;
|
|
this._dcImag = 0.9999f * this._dcImag + 0.0001f * num4;
|
|
this._cpxPtr[i].Real = num3 - this._dcReal;
|
|
this._cpxPtr[i].Imag = num4 - this._dcImag;
|
|
}
|
|
this._cuteFir.ProcessFilter(length, this._cpxPtr, this._cpxPtr);
|
|
for (int j = 0; j < length; j++)
|
|
{
|
|
Complex complex = this._cpxPtr[j];
|
|
rPtr[j] = (complex.Real - complex.Imag) * 0.002f;
|
|
lPtr[j] = (complex.Real + complex.Imag) * 0.002f;
|
|
}
|
|
}
|
|
}
|
|
}
|