2023-09-04 19:06:44 +01:00
// ARDOP Modem Decode Sound Samples
# include <math.h>
# include "ARDOPC.h"
# pragma warning(disable : 4244) // Code does lots of float to int
# ifndef TEENSY
# define MEMORYARQ
# endif
# undef PLOTWATERFALL
# ifdef PLOTWATERFALL
# define WHITE 0xffff
# define Tomato 0xffff
# define Orange 0xffff
# define Khaki 0xffff
# define Cyan 0xffff
# define DeepSkyBlue 0
# define RoyalBlue 0
# define Navy 0
# define Black 0
# endif
# ifdef TEENSY
# define PKTLED LED3 // flash when packet received
extern unsigned int PKTLEDTimer ;
# endif
//#define max(x, y) ((x) > (y) ? (x) : (y))
//#define min(x, y) ((x) < (y) ? (x) : (y))
void SendFrametoHost ( unsigned char * data , unsigned dlen ) ;
void CheckandAdjustRXLevel ( int maxlevel , int minlevel , BOOL Force ) ;
void mySetPixel ( unsigned char x , unsigned char y , unsigned int Colour ) ;
void clearDisplay ( ) ;
void updateDisplay ( ) ;
VOID L2Routine ( UCHAR * Packet , int Length , int FrameQuality , int totalRSErrors , int NumCar , int pktRXMode ) ;
void RemoveProcessedOFDMData ( ) ;
BOOL CheckCRC16 ( unsigned char * Data , int Length ) ;
void DrawAxes ( int Qual , const char * Frametype , char * Mode ) ;
extern int lastmax , lastmin ; // Sample Levels
char strRcvFrameTag [ 32 ] ;
BOOL blnLeaderFound = FALSE ;
int intLeaderRcvdMs = 1000 ; // Leader length??
extern int intLastRcvdFrameQuality ;
extern int intReceivedLeaderLen ;
extern UCHAR bytLastReceivedDataFrameType ;
extern int NErrors ;
extern BOOL blnBREAKCmd ;
extern UCHAR bytLastACKedDataFrameType ;
extern int intARQDefaultDlyMs ;
unsigned int tmrFinalID ;
extern BOOL PKTCONNECTED ;
extern int LastDemodType ;
extern int pktRXMode ;
extern int RXOFDMMode ;
extern BOOL blnBusyStatus ;
BOOL blnLastBusyStatus ;
int BusyCount ;
short intPriorMixedSamples [ 120 ] ; // a buffer of 120 samples to hold the prior samples used in the filter
int intPriorMixedSamplesLength = 120 ; // size of Prior sample buffer
// While searching for leader we must save unprocessed samples
// We may have up to 720 left, so need 1920
short rawSamples [ 2400 ] ; // Get Frame Type need 2400 and we may add 1200
extern int rawSamplesLength ;
int maxrawSamplesLength ;
short intFilteredMixedSamples [ 3500 ] ; // Get Frame Type need 2400 and we may add 1200
int intFilteredMixedSamplesLength = 0 ;
int MaxFilteredMixedSamplesLength = 0 ;
int intFrameType = 0 ; // Type we are decoding
int LastDataFrameType = 0 ; // Last data frame processed (for Memory ARQ, etc)
char strDecodeCapture [ 256 ] ;
// Frame type parameters
int intCenterFreq = 1500 ;
float floatCarFreq ; //(was int) // Are these the same ??
int intNumCar ;
int intSampPerSym ;
int intBaud ;
int intDataLen ;
int intRSLen ;
int intSampleLen ;
int DataRate = 0 ; // For SCS Reporting
int intDataPtr ;
int intDataBytesPerCar ;
BOOL blnOdd ;
char strType [ 18 ] = " " ;
char strMod [ 16 ] = " " ;
UCHAR bytMinQualThresh ;
int intPSKMode ;
int intSymbolsPerByte = 4 ;
// ARDOP V2 has max 10 carriers and 160 (120 + 40RS) per carrier
# define MAX_RAW_LENGTH 163 // Len Byte + Data + RS + CRC I think!
# define MAX_RAW_LENGTH_FSK 43 // MAX FSK 32 data + 8 RS
// OFDM is MAXCAR * 100
// 10 carrier 16QAM id 10 * 160
# define MAX_DATA_LENGTH MAXCAR * 100 // I think! (OFDM 16QAM)
// intToneMags should be an array with one row per carrier.
// and 16 * max bytes data (2 bits per symbol, 4 samples per symbol in 4FSK.
// but as 600 Baud frames are very long (750 bytes), but only one carrier
// may be better to store as scalar and calculate offsets into it for each carrier
// treat 600 as 3 * 200, but scalar still may be better
// Needs 64K if ints + another 64 for MEM ARQ. (maybe able to store as shorts)
// 48K would do if we use a scalar (600 baud, 750 bytes)
// Max is 4 carrier, 83 bytes or 1 carrier 762 (or treat as 3 * 253)
// Could just about do this on Teensy 3.6 or Nucleo F7
// looks like we have 4 samples for each 2 bits, which means 16 samples per byte.
// ARDOP 2 only has one and two carrier FSK modes
// Teensy is rather short of RAM, but as we never receive FSK and PSK
// at the same time we can use same data area (saves about 20K)
int intToneMagsIndex [ 2 ] ;
// Same here
int intSumCounts [ MAXCAR ] ; // number in above arrays
int intToneMagsLength ;
unsigned char goodCarriers = 0 ; // Carriers we have already decoded
// We always collect all phases for PSK and QAM so we can do phase correction
// Max PSK frame is 83, 4 samples per byte = 332
// Max 16QAM frame is 163, 2 samples per byte = 326
// OFDM frames are shorter, so carriers 11 - 17 could have smaller sample buffers
// This is a bit complicated, but allows smaller buffers for the OFDM carriers (Needed for Teensy)
//short intPhases[MAXCAR][332] = {0};
short QAMPhases [ 10 ] [ 332 ] ; // 6640 bytes
short OFDMPhases [ MAXCAR - 10 ] [ 232 ] ; // Need 232 = (PSK2 8 * 29); 15312
short * Phaseptrs [ MAXCAR ] =
{ & QAMPhases [ 0 ] [ 0 ] , & QAMPhases [ 1 ] [ 0 ] , & QAMPhases [ 2 ] [ 0 ] , & QAMPhases [ 3 ] [ 0 ] , & QAMPhases [ 4 ] [ 0 ] ,
& QAMPhases [ 5 ] [ 0 ] , & QAMPhases [ 6 ] [ 0 ] , & QAMPhases [ 7 ] [ 0 ] , & QAMPhases [ 8 ] [ 0 ] , & QAMPhases [ 9 ] [ 0 ] ,
& OFDMPhases [ 0 ] [ 0 ] , & OFDMPhases [ 1 ] [ 0 ] , & OFDMPhases [ 2 ] [ 0 ] , & OFDMPhases [ 3 ] [ 0 ] , & OFDMPhases [ 4 ] [ 0 ] ,
& OFDMPhases [ 5 ] [ 0 ] , & OFDMPhases [ 6 ] [ 0 ] , & OFDMPhases [ 7 ] [ 0 ] , & OFDMPhases [ 8 ] [ 0 ] , & OFDMPhases [ 9 ] [ 0 ] ,
& OFDMPhases [ 10 ] [ 0 ] , & OFDMPhases [ 11 ] [ 0 ] , & OFDMPhases [ 12 ] [ 0 ] , & OFDMPhases [ 13 ] [ 0 ] , & OFDMPhases [ 14 ] [ 0 ] ,
& OFDMPhases [ 15 ] [ 0 ] , & OFDMPhases [ 16 ] [ 0 ] , & OFDMPhases [ 17 ] [ 0 ] , & OFDMPhases [ 18 ] [ 0 ] , & OFDMPhases [ 19 ] [ 0 ] ,
& OFDMPhases [ 20 ] [ 0 ] , & OFDMPhases [ 21 ] [ 0 ] , & OFDMPhases [ 22 ] [ 0 ] , & OFDMPhases [ 23 ] [ 0 ] , & OFDMPhases [ 24 ] [ 0 ] ,
& OFDMPhases [ 25 ] [ 0 ] , & OFDMPhases [ 26 ] [ 0 ] , & OFDMPhases [ 27 ] [ 0 ] , & OFDMPhases [ 28 ] [ 0 ] , & OFDMPhases [ 29 ] [ 0 ] ,
& OFDMPhases [ 30 ] [ 0 ] , & OFDMPhases [ 31 ] [ 0 ] , & OFDMPhases [ 32 ] [ 0 ] } ;
short * * intPhases = & Phaseptrs [ 0 ] ;
short QAMMags [ 10 ] [ 332 ] ;
short OFDMMags [ MAXCAR - 10 ] [ 232 ] ;
short * Magptrs [ MAXCAR ] =
{ & QAMMags [ 0 ] [ 0 ] , & QAMMags [ 1 ] [ 0 ] , & QAMMags [ 2 ] [ 0 ] , & QAMMags [ 3 ] [ 0 ] , & QAMMags [ 4 ] [ 0 ] ,
& QAMMags [ 5 ] [ 0 ] , & QAMMags [ 6 ] [ 0 ] , & QAMMags [ 7 ] [ 0 ] , & QAMMags [ 8 ] [ 0 ] , & QAMMags [ 9 ] [ 0 ] ,
& OFDMMags [ 0 ] [ 0 ] , & OFDMMags [ 1 ] [ 0 ] , & OFDMMags [ 2 ] [ 0 ] , & OFDMMags [ 3 ] [ 0 ] , & OFDMMags [ 4 ] [ 0 ] ,
& OFDMMags [ 5 ] [ 0 ] , & OFDMMags [ 6 ] [ 0 ] , & OFDMMags [ 7 ] [ 0 ] , & OFDMMags [ 8 ] [ 0 ] , & OFDMMags [ 9 ] [ 0 ] ,
& OFDMMags [ 10 ] [ 0 ] , & OFDMMags [ 11 ] [ 0 ] , & OFDMMags [ 12 ] [ 0 ] , & OFDMMags [ 13 ] [ 0 ] , & OFDMMags [ 14 ] [ 0 ] ,
& OFDMMags [ 15 ] [ 0 ] , & OFDMMags [ 16 ] [ 0 ] , & OFDMMags [ 17 ] [ 0 ] , & OFDMMags [ 18 ] [ 0 ] , & OFDMMags [ 19 ] [ 0 ] ,
& OFDMMags [ 20 ] [ 0 ] , & OFDMMags [ 21 ] [ 0 ] , & OFDMMags [ 22 ] [ 0 ] , & OFDMMags [ 23 ] [ 0 ] , & OFDMMags [ 24 ] [ 0 ] ,
& OFDMMags [ 25 ] [ 0 ] , & OFDMMags [ 26 ] [ 0 ] , & OFDMMags [ 27 ] [ 0 ] , & OFDMMags [ 28 ] [ 0 ] , & OFDMMags [ 29 ] [ 0 ] ,
& OFDMMags [ 30 ] [ 0 ] , & OFDMMags [ 31 ] [ 0 ] , & OFDMMags [ 32 ] [ 0 ] } ;
short * * intMags = & Magptrs [ 0 ] ;
//int Tones[2][16 * MAX_RAW_LENGTH_FSK];
int intToneMags [ 4 ] [ 16 * MAX_RAW_LENGTH_FSK ] = { 0 } ; // Need one per carrier
// We need 5504 bytes for FSK but can overlay on PSK data areas
//int * Toneptrs[2] = {(int *)&Tones[0][0], (int *)&Tones[1][0]};
//int ** intToneMags = &Toneptrs[0];
# ifdef MEMORYARQ
// Enough RAM for memory ARQ so keep all samples for FSK and a copy of tones or phase/amplitude
int intToneMagsAvg [ 2 ] [ 332 ] ; //???? FSK Tone averages
short intCarPhaseAvg [ MAXCAR ] [ 332 ] ; // array to accumulate phases for averaging (Memory ARQ)
short intCarMagAvg [ MAXCAR ] [ 332 ] ; // array to accumulate mags for averaging (Memory ARQ)
# endif
//219 /3 * 8= 73 * 8 = 584
//163 * 4 = 652
// If we do Mem ARQ we will need a fair amount of RAM
int intPhasesLen ;
// Received Frame
UCHAR bytData [ 128 * 80 ] ; // Max OFDM Window
int frameLen ;
int totalRSErrors ;
// for comparing with CarrierOK
const char Good [ MAXCAR ] = { 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 } ; // All Good
const char Bad [ MAXCAR ] = { 0 } ; // All bad
// We need one raw buffer per carrier
// This can be optimized quite a bit to save space
// We can probably overlay on bytData
// If we still have 600 baud modes may need a lot more for first
// Note OFDM doesn't need one per carrier so only need 10
UCHAR bytFrameData [ 10 ] [ MAX_RAW_LENGTH + 10 ] ; // Received chars
char CarrierOk [ MAXCAR ] ; // RS OK Flags per carrier
int RepeatedFrame = 0 ; // set if this dats frame is a repeat
int charIndex = 0 ; // Index into received chars
int SymbolsLeft ; // number still to decode
int DummyCarrier = 0 ; // pseudo carrier used for long 600 baud frames
UCHAR * Decode600Buffer ;
BOOL PSKInitDone = FALSE ;
BOOL blnSymbolSyncFound , blnFrameSyncFound ;
extern UCHAR bytLastARQSessionID ;
extern UCHAR bytCurrentFrameType ;
extern int intShiftUpDn ;
extern const char ARQSubStates [ 10 ] [ 11 ] ;
extern int intLastARQDataFrameToHost ;
// dont think I need it short intRcvdSamples[12000]; // 1 second. May need to optimise
float dblOffsetLastGoodDecode = 0 ;
int dttLastGoodFrameTypeDecode = - 20000 ;
float dblOffsetHz = 0 ; ;
int dttLastLeaderDetect ;
extern int intRmtLeaderMeasure ;
extern BOOL blnARQConnected ;
extern BOOL blnPending ;
extern UCHAR bytPendingSessionID ;
extern UCHAR bytSessionID ;
int dttLastGoodFrameTypeDecod ;
int dttStartRmtLeaderMeasure ;
char lastGoodID [ 11 ] = " " ;
int GotBitSyncTicks ;
int intARQRTmeasuredMs ;
float dbl2Pi = 2 * M_PI ;
float dblSNdBPwr ;
float dblNCOFreq = 3000 ; // nominal NC) frequency
float dblNCOPhase = 0 ;
float dblNCOPhaseInc = 2 * M_PI * 3000 / 12000 ; // was dblNCOFreq
float dblPwrSNPower_dBPrior = 0 ;
float dblPhaseDiff1_2Avg ; // an initial value of -10 causes initialization in AcquireFrameSyncRSBAvg
int intMFSReadPtr = 0 ; // reset the MFSReadPtr offset 30 to accomodate the filter delay
int RcvdSamplesLen = 0 ; // Samples in RX buffer
float dblPhaseDiff1_2Avg ;
int intPhaseError = 0 ;
BOOL Acquire2ToneLeaderSymbolFraming ( ) ;
BOOL SearchFor2ToneLeader4 ( short * intNewSamples , int Length , float * dblOffsetHz , int * intSN ) ;
BOOL AcquireFrameSyncRSB ( ) ;
BOOL AcquireFrameSyncRSBAvg ( ) ;
int Acquire4FSKFrameType ( ) ;
void DemodulateFrame ( int intFrameType ) ;
void Demod1Car4FSKChar ( int Start , UCHAR * Decoded , int Carrier ) ;
VOID Track1Car4FSK ( short * intSamples , int * intPtr , int intSampPerSymbol , float intSearchFreq , int intBaud , UCHAR * bytSymHistory ) ;
VOID Decode1CarPSK ( int Carrier , BOOL OFDM ) ;
int EnvelopeCorrelator ( ) ;
int EnvelopeCorrelatorNew ( ) ;
2023-09-12 21:38:15 +01:00
BOOL DecodeFrame ( int chan , int intFrameType , UCHAR * bytData ) ;
2023-09-04 19:06:44 +01:00
void Update4FSKConstellation ( int * intToneMags , int * intQuality ) ;
void Update16FSKConstellation ( int * intToneMags , int * intQuality ) ;
void Update8FSKConstellation ( int * intToneMags , int * intQuality ) ;
void ProcessPingFrame ( char * bytData ) ;
int Compute4FSKSN ( ) ;
void DemodPSK ( ) ;
BOOL DemodQAM ( ) ;
BOOL DemodOFDM ( ) ;
BOOL Decode4FSKOFDMACK ( ) ;
void PrintCarrierFlags ( )
{
char Msg [ 128 ] ;
if ( intNumCar = = 1 )
Debugprintf ( " MEMARQ Flags %d " , CarrierOk [ 0 ] ) ;
else if ( intNumCar = = 2 )
Debugprintf ( " MEMARQ Flags %d %d " , CarrierOk [ 0 ] , CarrierOk [ 1 ] ) ;
else
{
sprintf ( Msg , " MEMARQ Flags %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d " ,
CarrierOk [ 0 ] , CarrierOk [ 1 ] , CarrierOk [ 2 ] , CarrierOk [ 3 ] , CarrierOk [ 4 ] , CarrierOk [ 5 ] , CarrierOk [ 6 ] , CarrierOk [ 7 ] , CarrierOk [ 8 ] , CarrierOk [ 9 ] ,
CarrierOk [ 10 ] , CarrierOk [ 11 ] , CarrierOk [ 12 ] , CarrierOk [ 13 ] , CarrierOk [ 14 ] , CarrierOk [ 15 ] , CarrierOk [ 16 ] , CarrierOk [ 17 ] , CarrierOk [ 18 ] , CarrierOk [ 19 ] ,
CarrierOk [ 20 ] , CarrierOk [ 21 ] , CarrierOk [ 22 ] , CarrierOk [ 23 ] , CarrierOk [ 24 ] , CarrierOk [ 25 ] , CarrierOk [ 26 ] , CarrierOk [ 27 ] , CarrierOk [ 28 ] , CarrierOk [ 29 ] ,
CarrierOk [ 30 ] , CarrierOk [ 31 ] , CarrierOk [ 32 ] , CarrierOk [ 33 ] , CarrierOk [ 34 ] , CarrierOk [ 35 ] , CarrierOk [ 36 ] , CarrierOk [ 37 ] , CarrierOk [ 38 ] , CarrierOk [ 39 ] ,
CarrierOk [ 40 ] , CarrierOk [ 41 ] , CarrierOk [ 42 ] ) ;
Msg [ 12 + 2 * intNumCar ] = 0 ;
Debugprintf ( Msg ) ;
}
}
// Function to determine if frame type is short control frame
BOOL IsShortControlFrame ( UCHAR bytType )
{
switch ( intFrameType )
{
case DataNAK :
case DataNAKLoQ :
case ConRejBusy :
case ConRejBW :
case ConAck :
case DISCFRAME :
case BREAK :
case END :
case IDLEFRAME :
case DataACK :
case DataACKHiQ :
return TRUE ;
}
return FALSE ;
}
BOOL IsConReqFrame ( UCHAR bytType )
{
switch ( bytType )
{
case ConReq200 :
case ConReq500 :
case ConReq2500 :
case OConReq500 :
case OConReq2500 :
return TRUE ;
}
return FALSE ;
}
// Function to determine if it is a data frame (Even OR Odd)
BOOL IsDataFrame ( UCHAR intFrameType )
{
const char * String = Name ( intFrameType ) ;
if ( intFrameType = = PktFrameHeader )
return TRUE ;
if ( String = = NULL | | String [ 0 ] = = 0 )
return FALSE ;
if ( strstr ( String , " .E " ) | | strstr ( String , " .O " ) )
return TRUE ;
return FALSE ;
}
// Subroutine to clear all mixed samples
void ClearAllMixedSamples ( )
{
intFilteredMixedSamplesLength = 0 ;
intMFSReadPtr = 0 ;
rawSamplesLength = 0 ; // Clear saved
}
// Subroutine to Initialize mixed samples
void InitializeMixedSamples ( )
{
// Measure the time from release of PTT to leader detection of reply.
intARQRTmeasuredMs = min ( 10000 , Now - dttStartRTMeasure ) ; //?????? needs work
intPriorMixedSamplesLength = 120 ; // zero out prior samples in Prior sample buffer
intFilteredMixedSamplesLength = 0 ; // zero out the FilteredMixedSamples array
intMFSReadPtr = 0 ; // reset the MFSReadPtr offset 30 to accomodate the filter delay
}
// Subroutine to discard all sampled prior to current intRcvdSamplesRPtr
void DiscardOldSamples ( )
{
// This restructures the intRcvdSamples array discarding all samples prior to intRcvdSamplesRPtr
//not sure why we need this !!
/*
if ( RcvdSamplesLen - intRcvdSamplesRPtr < = 0 )
RcvdSamplesLen = intRcvdSamplesRPtr = 0 ;
else
{
// This is rather slow. I'd prefer a cyclic buffer. Lets see....
memmove ( intRcvdSamples , & intRcvdSamples [ intRcvdSamplesRPtr ] , ( RcvdSamplesLen - intRcvdSamplesRPtr ) * 2 ) ;
RcvdSamplesLen - = intRcvdSamplesRPtr ;
intRcvdSamplesRPtr = 0 ;
}
*/
}
// Subroutine to apply 2000 Hz filter to mixed samples
float xdblZin_1 = 0 , xdblZin_2 = 0 , xdblZComb = 0 ; // Used in the comb generator
// The resonators
float xdblZout_0 [ 29 ] = { 0.0f } ; // resonator outputs
float xdblZout_1 [ 29 ] = { 0.0f } ; // resonator outputs delayed one sample
float xdblZout_2 [ 29 ] = { 0.0f } ; // resonator outputs delayed two samples
float xdblCoef [ 29 ] = { 0.0 } ; // the coefficients
float xdblR = 0.9995f ; // insures stability (must be < 1.0) (Value .9995 7/8/2013 gives good results)
int xintN = 120 ; //Length of filter 12000/100
void FSMixFilter2500Hz ( short * intMixedSamples , int intMixedSamplesLength )
{
// assumes sample rate of 12000
// implements 27 100 Hz wide sections (~2500 Hz wide @ - 30dB centered on 1500 Hz)
// FSF (Frequency Selective Filter) variables
// This works on intMixedSamples, len intMixedSamplesLength;
// Filtered data is appended to intFilteredMixedSamples
float dblRn ;
float dblR2 ;
float dblZin = 0 ;
int i , j ;
float intFilteredSample = 0 ; // Filtered sample
if ( intFilteredMixedSamplesLength < 0 )
Debugprintf ( " Corrupt intFilteredMixedSamplesLength " ) ;
dblRn = powf ( xdblR , xintN ) ;
dblR2 = powf ( xdblR , 2 ) ;
// Initialize the coefficients
if ( xdblCoef [ 28 ] = = 0 )
{
for ( i = 2 ; i < = 28 ; i + + )
{
xdblCoef [ i ] = 2 * xdblR * cosf ( 2 * M_PI * i / xintN ) ; // For Frequency = bin i
}
}
for ( i = 0 ; i < intMixedSamplesLength ; i + + )
{
intFilteredSample = 0 ;
if ( i < xintN )
dblZin = intMixedSamples [ i ] - dblRn * intPriorMixedSamples [ i ] ;
else
dblZin = intMixedSamples [ i ] - dblRn * intMixedSamples [ i - xintN ] ;
//Compute the Comb
xdblZComb = dblZin - xdblZin_2 * dblR2 ;
xdblZin_2 = xdblZin_1 ;
xdblZin_1 = dblZin ;
// Now the resonators
for ( j = 2 ; j < = 28 ; j + + ) // calculate output for 3 resonators
{
xdblZout_0 [ j ] = xdblZComb + xdblCoef [ j ] * xdblZout_1 [ j ] - dblR2 * xdblZout_2 [ j ] ;
xdblZout_2 [ j ] = xdblZout_1 [ j ] ;
xdblZout_1 [ j ] = xdblZout_0 [ j ] ;
//' scale each by transition coeff and + (Even) or - (Odd)
//' Resonators 2 and 13 scaled by .389 get best shape and side lobe supression
//' Scaling also accomodates for the filter "gain" of approx 60.
if ( j = = 2 | | j = = 28 )
intFilteredSample + = 0.389f * xdblZout_0 [ j ] ;
else if ( ( j & 1 ) = = 0 )
intFilteredSample + = xdblZout_0 [ j ] ;
else
intFilteredSample - = xdblZout_0 [ j ] ;
}
intFilteredSample = intFilteredSample * 0.00833333333f ;
intFilteredMixedSamples [ intFilteredMixedSamplesLength + + ] = intFilteredSample ; // rescales for gain of filter
}
// update the prior intPriorMixedSamples array for the next filter call
memmove ( intPriorMixedSamples , & intMixedSamples [ intMixedSamplesLength - xintN ] , intPriorMixedSamplesLength * 2 ) ;
if ( intFilteredMixedSamplesLength > MaxFilteredMixedSamplesLength )
MaxFilteredMixedSamplesLength = intFilteredMixedSamplesLength ;
if ( intFilteredMixedSamplesLength > 3500 )
Debugprintf ( " Corrupt intFilteredMixedSamplesLength %d " , intFilteredMixedSamplesLength ) ;
}
// Function to apply 150Hz filter used in Envelope correlator
void Filter150Hz ( short * intFilterOut )
{
// assumes sample rate of 12000
// implements 3 100 Hz wide sections (~150 Hz wide @ - 30dB centered on 1500 Hz)
// FSF (Frequency Selective Filter) variables
static float dblR = 0.9995f ; // insures stability (must be < 1.0) (Value .9995 7/8/2013 gives good results)
static int intN = 120 ; //Length of filter 12000/100
static float dblRn ;
static float dblR2 ;
static float dblCoef [ 17 ] = { 0.0 } ; // the coefficients
float dblZin = 0 , dblZin_1 = 0 , dblZin_2 = 0 , dblZComb = 0 ; // Used in the comb generator
// The resonators
float dblZout_0 [ 17 ] = { 0.0 } ; // resonator outputs
float dblZout_1 [ 17 ] = { 0.0 } ; // resonator outputs delayed one sample
float dblZout_2 [ 17 ] = { 0.0 } ; // resonator outputs delayed two samples
int i , j ;
float FilterOut = 0 ; // Filtered sample
float largest = 0 ;
dblRn = powf ( dblR , intN ) ;
dblR2 = powf ( dblR , 2 ) ;
// Initialize the coefficients
if ( dblCoef [ 17 ] = = 0 )
{
for ( i = 14 ; i < = 16 ; i + + )
{
dblCoef [ i ] = 2 * dblR * cosf ( 2 * M_PI * i / intN ) ; // For Frequency = bin i
}
}
for ( i = 0 ; i < 480 ; i + + )
{
if ( i < intN )
dblZin = intFilteredMixedSamples [ intMFSReadPtr + i ] - dblRn * 0 ; // no prior mixed samples
else
dblZin = intFilteredMixedSamples [ intMFSReadPtr + i ] - dblRn * intFilteredMixedSamples [ intMFSReadPtr + i - intN ] ;
// Compute the Comb
dblZComb = dblZin - dblZin_2 * dblR2 ;
dblZin_2 = dblZin_1 ;
dblZin_1 = dblZin ;
// Now the resonators
for ( j = 14 ; j < = 16 ; j + + ) // calculate output for 3 resonators
{
dblZout_0 [ j ] = dblZComb + dblCoef [ j ] * dblZout_1 [ j ] - dblR2 * dblZout_2 [ j ] ;
dblZout_2 [ j ] = dblZout_1 [ j ] ;
dblZout_1 [ j ] = dblZout_0 [ j ] ;
// scale each by transition coeff and + (Even) or - (Odd)
// Scaling also accomodates for the filter "gain" of approx 120.
// These transition coefficients fairly close to optimum for WGN 0db PSK4, 100 baud (yield highest average quality) 5/24/2014
if ( j = = 14 | | j = = 16 )
FilterOut = 0.2f * dblZout_0 [ j ] ; // this transisiton minimizes ringing and peaks
else
FilterOut - = dblZout_0 [ j ] ;
}
intFilterOut [ i ] = ( int ) ceil ( FilterOut * 0.00833333333 ) ; // rescales for gain of filter
}
}
// Function to apply 75Hz filter used in Envelope correlator
void Filter75Hz ( short * intFilterOut , BOOL blnInitialise , int intSamplesToFilter )
{
// assumes sample rate of 12000
// implements 3 100 Hz wide sections (~150 Hz wide @ - 30dB centered on 1500 Hz)
// FSF (Frequency Selective Filter) variables
static float dblR = 0.9995f ; // insures stability (must be < 1.0) (Value .9995 7/8/2013 gives good results)
static int intN = 240 ; //Length of filter 12000/50 - delays output 120 samples from input
static float dblRn ;
static float dblR2 ;
static float dblCoef [ 3 ] = { 0.0 } ; // the coefficients
float dblZin = 0 , dblZin_1 = 0 , dblZin_2 = 0 , dblZComb = 0 ; // Used in the comb generator
// The resonators
float dblZout_0 [ 3 ] = { 0.0 } ; // resonator outputs
float dblZout_1 [ 3 ] = { 0.0 } ; // resonator outputs delayed one sample
float dblZout_2 [ 3 ] = { 0.0 } ; // resonator outputs delayed two samples
int i , j ;
float FilterOut = 0 ; // Filtered sample
float largest = 0 ;
dblRn = powf ( dblR , intN ) ;
dblR2 = powf ( dblR , 2 ) ;
// Initialize the coefficients
if ( dblCoef [ 2 ] = = 0 )
{
for ( i = 0 ; i < = 3 ; i + + )
{
dblCoef [ i ] = 2 * dblR * cosf ( 2 * M_PI * ( 29 + i ) / intN ) ; // For Frequency = bin 29, 30, 31
}
}
for ( i = 0 ; i < intSamplesToFilter ; i + + )
{
if ( i < intN )
dblZin = intFilteredMixedSamples [ intMFSReadPtr + i ] - dblRn * 0 ; // no prior mixed samples
else
dblZin = intFilteredMixedSamples [ intMFSReadPtr + i ] - dblRn * intFilteredMixedSamples [ intMFSReadPtr + i - intN ] ;
// Compute the Comb
dblZComb = dblZin - dblZin_2 * dblR2 ;
dblZin_2 = dblZin_1 ;
dblZin_1 = dblZin ;
// Now the resonators
for ( j = 0 ; j < 3 ; j + + ) // calculate output for 3 resonators
{
dblZout_0 [ j ] = dblZComb + dblCoef [ j ] * dblZout_1 [ j ] - dblR2 * dblZout_2 [ j ] ;
dblZout_2 [ j ] = dblZout_1 [ j ] ;
dblZout_1 [ j ] = dblZout_0 [ j ] ;
// scale each by transition coeff and + (Even) or - (Odd)
// Scaling also accomodates for the filter "gain" of approx 120.
// These transition coefficients fairly close to optimum for WGN 0db PSK4, 100 baud (yield highest average quality) 5/24/2014
if ( j = = 0 | | j = = 2 )
FilterOut - = 0.39811f * dblZout_0 [ j ] ; // this transisiton minimizes ringing and peaks
else
FilterOut + = dblZout_0 [ j ] ;
}
intFilterOut [ i ] = ( int ) ceil ( FilterOut * 0.0041f ) ; // rescales for gain of filter
}
}
// Subroutine to Mix new samples with NCO to tune to nominal 1500 Hz center with reversed sideband and filter.
void MixNCOFilter ( short * intNewSamples , int Length , float dblOffsetHz )
{
// Correct the dimension of intPriorMixedSamples if needed (should only happen after a bandwidth setting change).
int i ;
short intMixedSamples [ 2400 ] ; // All we need at once ( I hope!) // may need to be int
int intMixedSamplesLength = 0 ; //size of intMixedSamples
if ( Length = = 0 )
return ;
// Nominal NCO freq is 3000 Hz to downmix intNewSamples (NCO - Fnew) to center of 1500 Hz (invertes the sideband too)
dblNCOFreq = 3000 + dblOffsetHz ;
dblNCOPhaseInc = dblNCOFreq * dbl2Pi / 12000 ;
intMixedSamplesLength = Length ;
for ( i = 0 ; i < Length ; i + + )
{
intMixedSamples [ i ] = ( int ) ceilf ( intNewSamples [ i ] * cosf ( dblNCOPhase ) ) ; // later may want a lower "cost" implementation of "Cos"
dblNCOPhase + = dblNCOPhaseInc ;
if ( dblNCOPhase > dbl2Pi )
dblNCOPhase - = dbl2Pi ;
}
// showed no significant difference if the 2000 Hz filer used for all bandwidths.
// printtick("Start Filter");
FSMixFilter2500Hz ( intMixedSamples , intMixedSamplesLength ) ; // filter through the FS filter (required to reject image from Local oscillator)
// printtick("Done Filter");
// save for analysys
// WriteSamples(&intFilteredMixedSamples[oldlen], Length);
// WriteSamples(intMixedSamples, Length);
}
// Function to Correct Raw demodulated data with Reed Solomon FEC
int CorrectRawDataWithRS ( UCHAR * bytRawData , UCHAR * bytCorrectedData , int intDataLen , int intRSLen , int bytFrameType , int Carrier )
{
BOOL blnRSOK ;
BOOL FrameOK ;
BOOL OK ;
//Dim bytNoRS(1 + intDataLen + 2 - 1) As Byte ' 1 byte byte Count, Data, 2 byte CRC
//Array.Copy(bytRawData, 0, bytNoRS, 0, bytNoRS.Length)
if ( CarrierOk [ Carrier ] ) // Already decoded this carrier?
{
// Athough we have already checked the data, it may be in the wrong place
// in the buffer if another carrier was decoded wrong.
memcpy ( bytCorrectedData , & bytRawData [ 1 ] , bytRawData [ 0 ] + 1 ) ; // Extra char in case OFDM
if ( strFrameType [ LastDataFrameType ] [ 0 ] = = ' O ' )
Debugprintf ( " [CorrectRawDataWithRS] Carrier %d already decoded Block %d, Len %d " , Carrier , bytRawData [ 1 ] , bytRawData [ 0 ] ) ;
else
Debugprintf ( " [CorrectRawDataWithRS] Carrier %d already decoded Len %d " , Carrier , bytRawData [ 0 ] ) ;
return bytRawData [ 0 ] ; // don't do it again
}
if ( strFrameType [ intFrameType ] [ 0 ] = = ' O ' )
OK = CheckCRC16 ( bytRawData , intDataLen + 1 ) ;
else
OK = CheckCRC16FrameType ( bytRawData , intDataLen + 1 , bytFrameType ) ;
// As crc can fail also check returned lenght is reasonable
if ( OK & & bytRawData [ 0 ] < = intDataLen ) // No RS correction needed // return the actual data
{
memcpy ( bytCorrectedData , & bytRawData [ 1 ] , bytRawData [ 0 ] + 1 ) ;
if ( strFrameType [ intFrameType ] [ 0 ] = = ' O ' )
Debugprintf ( " [CorrectRawDataWithRS] Carrier %d OK without RS, Block %d Len = %d " , Carrier , bytRawData [ 1 ] , bytRawData [ 0 ] ) ;
else
Debugprintf ( " [CorrectRawDataWithRS] Carrier %d OK without RS, Len = %d " , Carrier , bytRawData [ 0 ] ) ;
CarrierOk [ Carrier ] = TRUE ;
return bytRawData [ 0 ] ;
}
// Try correcting with RS Parity
FrameOK = RSDecode ( bytRawData , intDataLen + 3 + intRSLen , intRSLen , & blnRSOK ) ;
if ( blnRSOK )
{ }
// Debugprintf("RS Says OK without correction");
else
if ( FrameOK )
{ }
// Debugprintf("RS Says OK after %d correction(s)", NErrors);
else
{
Debugprintf ( " [intFrameType] RS Says Can't Correct " ) ;
goto returnBad ;
}
if ( FrameOK )
{
if ( strFrameType [ intFrameType ] [ 0 ] = = ' O ' )
OK = CheckCRC16 ( bytRawData , intDataLen + 1 ) ;
else
OK = CheckCRC16FrameType ( bytRawData , intDataLen + 1 , bytFrameType ) ;
if ( OK & & bytRawData [ 0 ] < = intDataLen ) // Now OK - return the actual data
{
int intFailedByteCnt = 0 ;
if ( strFrameType [ intFrameType ] [ 0 ] = = ' O ' )
Debugprintf ( " [CorrectRawDataWithRS] Carrier %d OK with RS %d corrections, Block %d, Len = %d " , Carrier , NErrors , bytRawData [ 1 ] , bytRawData [ 0 ] ) ;
else
Debugprintf ( " [CorrectRawDataWithRS] Carrier %d OK with RS %d corrections, Len = %d " , Carrier , NErrors , bytRawData [ 0 ] ) ;
totalRSErrors + = NErrors ;
memcpy ( bytCorrectedData , & bytRawData [ 1 ] , bytRawData [ 0 ] + 1 ) ;
CarrierOk [ Carrier ] = TRUE ;
return bytRawData [ 0 ] ;
}
Debugprintf ( " [CorrectRawDataWithRS] Carrier %d RS says ok but CRC still bad " , Carrier ) ;
}
// return uncorrected data without byte count or RS Parity
returnBad :
memcpy ( bytCorrectedData , & bytRawData [ 1 ] , intDataLen + 1 ) ;
CarrierOk [ Carrier ] = FALSE ;
return intDataLen ;
}
// Subroutine to process new samples as received from the sound card via Main.ProcessCapturedData
// Only called when not transmitting
double dblPhaseInc ; // in milliradians
short intNforGoertzel [ MAXCAR ] ;
short intPSKPhase_1 [ MAXCAR ] , intPSKPhase_0 [ MAXCAR ] ;
short intCP [ MAXCAR ] ; // Cyclic prefix offset
float dblFreqBin [ MAXCAR ] ;
BOOL CheckFrameTypeParity ( int intTonePtr , int * intToneMags ) ;
2023-09-12 21:38:15 +01:00
void ARDOPProcessNewSamples ( int chan , short * Samples , int nSamples )
2023-09-04 19:06:44 +01:00
{
BOOL blnFrameDecodedOK = FALSE ;
// LookforUZ7HOLeader(Samples, nSamples);
// printtick("Start afsk");
// DemodAFSK(Samples, nSamples);
// printtick("End afsk");
// return;
if ( ProtocolState = = FECSend )
return ;
// Append new data to anything in rawSamples
memcpy ( & rawSamples [ rawSamplesLength ] , Samples , nSamples * 2 ) ;
rawSamplesLength + = nSamples ;
if ( rawSamplesLength > maxrawSamplesLength )
maxrawSamplesLength = rawSamplesLength ;
if ( rawSamplesLength > = 2400 )
Debugprintf ( " Corrupt rawSamplesLength %d " , rawSamplesLength ) ;
nSamples = rawSamplesLength ;
Samples = rawSamples ;
rawSamplesLength = 0 ;
// printtick("Start Busy");
if ( nSamples > = 1024 )
UpdateBusyDetector ( Samples ) ;
// printtick("Done Busy");
// it seems that searchforleader runs on unmixed and unfilered samples
// Searching for leader
if ( State = = SearchingForLeader )
{
// Search for leader as long as 960 samples (8 symbols) available
// printtick("Start Leader Search");
if ( nSamples > = 1200 )
{
if ( ProtocolState = = FECSend )
return ;
}
while ( State = = SearchingForLeader & & nSamples > = 1200 )
{
int intSN ;
blnLeaderFound = SearchFor2ToneLeader4 ( Samples , nSamples , & dblOffsetHz , & intSN ) ;
// blnLeaderFound = SearchFor2ToneLeader2(Samples, nSamples, &dblOffsetHz, &intSN);
if ( blnLeaderFound )
{
// Debugprintf("Got Leader");
dttLastLeaderDetect = Now ;
nSamples - = 480 ;
Samples + = 480 ; // !!!! needs attention !!!
InitializeMixedSamples ( ) ;
State = AcquireSymbolSync ;
}
else
{
if ( SlowCPU )
{
nSamples - = 480 ;
Samples + = 480 ; // advance pointer 2 symbols (40 ms) ' reduce CPU loading
}
else
{
nSamples - = 240 ;
Samples + = 240 ; // !!!! needs attention !!!
}
}
}
if ( State = = SearchingForLeader )
{
// Save unused samples
memmove ( rawSamples , Samples , nSamples * 2 ) ;
rawSamplesLength = nSamples ;
// printtick("End Leader Search");
return ;
}
}
// Got leader
// At this point samples haven't been processed, and are in Samples, len nSamples
// I'm going to filter all samples into intFilteredMixedSamples.
// printtick("Start Mix");
MixNCOFilter ( Samples , nSamples , dblOffsetHz ) ; // Mix and filter new samples (Mixing consumes all intRcvdSamples)
nSamples = 0 ; // all used
// printtick("Done Mix Samples");
// Acquire Symbol Sync
if ( State = = AcquireSymbolSync )
{
if ( ( intFilteredMixedSamplesLength - intMFSReadPtr ) > 960 )
{
blnSymbolSyncFound = Acquire2ToneLeaderSymbolFraming ( ) ; // adjust the pointer to the nominal symbol start based on phase
if ( blnSymbolSyncFound )
State = AcquireFrameSync ;
else
{
// Rick's Latest code (2.0.3) advances pointer instead of clearing samples
// DiscardOldSamples();
// ClearAllMixedSamples();
intMFSReadPtr + = 240 ; // advance the MFSReadPointer one symbol and try to search for leader again.
State = SearchingForLeader ;
return ;
}
// printtick("Got Sym Sync");
}
}
// Acquire Frame Sync
if ( State = = AcquireFrameSync )
{
blnFrameSyncFound = AcquireFrameSyncRSB ( ) ;
// Remove used samples
intFilteredMixedSamplesLength - = intMFSReadPtr ;
if ( intFilteredMixedSamplesLength < 0 )
Debugprintf ( " Corrupt intFilteredMixedSamplesLength " ) ;
memmove ( intFilteredMixedSamples ,
& intFilteredMixedSamples [ intMFSReadPtr ] , intFilteredMixedSamplesLength * 2 ) ;
intMFSReadPtr = 0 ;
if ( blnFrameSyncFound )
{
State = AcquireFrameType ;
// Have frame Sync. Remove used samples from buffer
printtick ( " Got Frame Sync " ) ;
}
else if ( ( Now - dttLastLeaderDetect ) > 1000 ) // no Frame sync within 1000 ms (may want to make this limit a funciton of Mode and leaders)
{
DiscardOldSamples ( ) ;
ClearAllMixedSamples ( ) ;
State = SearchingForLeader ;
printtick ( " frame sync timeout " ) ;
}
/*
else if ( intPhaseError > 2 )
{
DiscardOldSamples ( ) ;
ClearAllMixedSamples ( ) ;
State = SearchingForLeader ;
printtick ( " frame sync timeout " ) ;
}
// else
// printtick("no frame sync");
*/
}
// Acquire Frame Type
if ( State = = AcquireFrameType )
{
// printtick("getting frame type");
intFrameType = Acquire4FSKFrameType ( ) ;
if ( intFrameType = = - 2 )
{
// sprintf(Msg, "not enough %d %d", intFilteredMixedSamplesLength, intMFSReadPtr);
// printtick(Msg);
return ; // insufficient samples
}
if ( intFrameType = = - 1 ) // poor decode quality (large decode distance)
{
State = SearchingForLeader ;
ClearAllMixedSamples ( ) ;
DiscardOldSamples ( ) ;
Debugprintf ( " poor frame type decode " ) ;
// stcStatus.BackColor = SystemColors.Control
// stcStatus.Text = ""
// stcStatus.ControlName = "lblRcvFrame"
// queTNCStatus.Enqueue(stcStatus)
}
else
{
// Get Frame info and Initialise Demodulate variables
// We've used intMFSReadPtr samples, so remove from Buffer
// sprintf(Msg, "Got Frame Type %x", intFrameType);
// printtick(Msg);
blnLastBusyStatus = 1 ;
blnBusyStatus = 1 ;
BusyCount = 10 ;
intFilteredMixedSamplesLength - = intMFSReadPtr ;
if ( intFilteredMixedSamplesLength < 0 )
Debugprintf ( " Corrupt intFilteredMixedSamplesLength " ) ;
memmove ( intFilteredMixedSamples ,
& intFilteredMixedSamples [ intMFSReadPtr ] , intFilteredMixedSamplesLength * 2 ) ;
intMFSReadPtr = 0 ;
if ( ! FrameInfo ( intFrameType , & blnOdd , & intNumCar , strMod , & intBaud , & intDataLen , & intRSLen , & bytMinQualThresh , strType ) )
{
printtick ( " bad frame type " ) ;
State = SearchingForLeader ;
ClearAllMixedSamples ( ) ;
DiscardOldSamples ( ) ;
return ;
}
if ( IsShortControlFrame ( intFrameType ) )
{
// Frame has no data so is now complete
DrawRXFrame ( 1 , Name ( intFrameType ) ) ;
// See if IRStoISS shortcut can be invoked
// prepare for next
DiscardOldSamples ( ) ;
ClearAllMixedSamples ( ) ;
State = SearchingForLeader ;
blnFrameDecodedOK = TRUE ;
Debugprintf ( " [DecodeFrame] Frame: %s " , Name ( intFrameType ) ) ;
DecodeCompleteTime = Now ;
goto ProcessFrame ;
}
DrawRXFrame ( 0 , Name ( intFrameType ) ) ;
if ( intBaud = = 25 )
intSampPerSym = 480 ;
else if ( intBaud = = 50 )
intSampPerSym = 240 ;
else if ( intBaud = = 55 )
intSampPerSym = 216 ;
else if ( intBaud = = 100 )
intSampPerSym = 120 ;
else if ( intBaud = = 167 )
intSampPerSym = 72 ;
else if ( intBaud = = 600 )
intSampPerSym = 20 ;
if ( IsDataFrame ( intFrameType ) )
SymbolsLeft = intDataLen + intRSLen + 3 ; // Data has crc + length byte
else if ( intFrameType = = OFDMACK )
SymbolsLeft = intDataLen + intRSLen + 2 ; // CRC but no len
else
SymbolsLeft = intDataLen + intRSLen ; // No CRC
if ( intDataLen = = 600 )
SymbolsLeft + = 6 ; // 600 baud has 3 * RS Blocks
// Save data rate for PTC reporting
if ( Rate [ intFrameType ] > 0 )
DataRate = Rate [ intFrameType ] ;
intToneMagsLength = 16 * SymbolsLeft ; // 4 tones, 2 bits per set
memset ( intToneMagsIndex , 0 , sizeof ( intToneMagsIndex ) ) ;
charIndex = 0 ;
PSKInitDone = 0 ;
frameLen = 0 ;
totalRSErrors = 0 ;
DummyCarrier = 0 ; // pseudo carrier used for long 600 baud frames
Decode600Buffer = & bytFrameData [ 0 ] [ 0 ] ;
State = AcquireFrame ;
// if a data frame, and not the same frame type as last, reinitialise
// correctly received carriers byte and memory ARQ fields
// if (IsDataFrame(intFrameType) && LastDataFrameType != intFrameType)
if ( intFrameType = = PktFrameHeader | | intFrameType = = PktFrameData )
{
memset ( CarrierOk , 0 , sizeof ( CarrierOk ) ) ;
memset ( intSumCounts , 0 , sizeof ( intSumCounts ) ) ;
# ifdef MEMORYARQ
memset ( intToneMagsAvg , 0 , sizeof ( intToneMagsAvg ) ) ;
memset ( intCarPhaseAvg , 0 , sizeof ( intCarPhaseAvg ) ) ;
memset ( intCarMagAvg , 0 , sizeof ( intCarMagAvg ) ) ;
# endif
LastDataFrameType = intFrameType ;
}
else if ( LastDataFrameType ! = intFrameType )
{
if ( strFrameType [ LastDataFrameType ] [ 0 ] = = ' O ' )
{
// OFDM Frame, We know the ISS received the last ack, so can remove any data passed to host.
// We need to do that, as new frame block numbers will start from first unacked block.
if ( intFrameType = = OFDMACK )
RepeatedFrame = FALSE ;
RepeatedFrame = FALSE ;
RemoveProcessedOFDMData ( ) ;
}
Debugprintf ( " New frame type - MEMARQ flags reset " ) ;
memset ( CarrierOk , 0 , sizeof ( CarrierOk ) ) ;
if ( IsDataFrame ( intFrameType ) )
LastDataFrameType = intFrameType ;
// note that although we only do mem arq if enough RAM we
// still skip decoding carriers that have been received;
# ifdef MEMORYARQ
memset ( intSumCounts , 0 , sizeof ( intSumCounts ) ) ;
memset ( intToneMagsAvg , 0 , sizeof ( intToneMagsAvg ) ) ;
memset ( intCarPhaseAvg , 0 , sizeof ( intCarPhaseAvg ) ) ;
memset ( intCarMagAvg , 0 , sizeof ( intCarMagAvg ) ) ;
# endif
}
else
{
// Repeated frame. OFDM needs to know, as we may have passed data to host.
if ( IsDataFrame ( intFrameType ) )
RepeatedFrame = TRUE ;
}
PrintCarrierFlags ( ) ;
}
}
// Acquire Frame
if ( State = = AcquireFrame )
{
// Call DemodulateFrame for each set of samples
DemodulateFrame ( intFrameType ) ;
if ( State = = AcquireFrame )
// We haven't got it all yet so wait for more samples
return ;
// We have the whole frame, so process it
// printtick("got whole frame");
LastDemodType = intFrameType ;
if ( strcmp ( strMod , " 4FSK " ) = = 0 )
Update4FSKConstellation ( & intToneMags [ 0 ] [ 0 ] , & intLastRcvdFrameQuality ) ;
else if ( strcmp ( strMod , " 16FSK " ) = = 0 )
Update16FSKConstellation ( & intToneMags [ 0 ] [ 0 ] , & intLastRcvdFrameQuality ) ;
else if ( strcmp ( strMod , " 8FSK " ) = = 0 )
Update8FSKConstellation ( & intToneMags [ 0 ] [ 0 ] , & intLastRcvdFrameQuality ) ;
// PSK and QAM quality done in Decode routines
Debugprintf ( " Qual = %d " , intLastRcvdFrameQuality ) ;
// This mechanism is to skip actual decoding and reply/change state...no need to decode
2023-09-12 21:38:15 +01:00
blnFrameDecodedOK = DecodeFrame ( chan , intFrameType , bytData ) ;
2023-09-04 19:06:44 +01:00
ProcessFrame :
if ( ! blnFrameDecodedOK )
DrawRXFrame ( 2 , Name ( intFrameType ) ) ;
if ( intFrameType = = PktFrameData )
{
# ifdef TEENSY
SetLED ( PKTLED , TRUE ) ; // Flash LED
PKTLEDTimer = Now + 200 ; // for 200 mS
# endif
return ;
}
if ( blnFrameDecodedOK )
{
// Set input level if supported
# ifdef HASPOTS
CheckandAdjustRXLevel ( lastmax , lastmin , TRUE ) ;
# endif
if ( AccumulateStats )
if ( IsDataFrame ( intFrameType ) )
if ( strstr ( strMod , " PSK " ) )
intGoodPSKFrameDataDecodes + + ;
else if ( strstr ( strMod , " QAM " ) )
intGoodQAMFrameDataDecodes + + ;
else if ( strstr ( strMod , " OFDM " ) )
intGoodOFDMFrameDataDecodes + + ;
else
intGoodFSKFrameDataDecodes + + ;
# ifdef TEENSY
if ( IsDataFrame ( intFrameType ) )
{
SetLED ( PKTLED , TRUE ) ; // Flash LED
PKTLEDTimer = Now + 400 ; // For 400 Ms
}
# endif
}
else
{
// Bad decode
if ( AccumulateStats )
if ( IsDataFrame ( intFrameType ) )
if ( strstr ( strMod , " PSK " ) )
intFailedPSKFrameDataDecodes + + ;
else if ( strstr ( strMod , " QAM " ) )
intFailedQAMFrameDataDecodes + + ;
else if ( strstr ( strMod , " OFDM " ) )
intFailedOFDMFrameDataDecodes + + ;
else
intFailedFSKFrameDataDecodes + + ;
// Debug.WriteLine("[DecodePSKData2] bytPass = " & Format(bytPass, "X"))
}
skipDecode :
State = SearchingForLeader ;
ClearAllMixedSamples ( ) ;
DiscardOldSamples ( ) ;
return ;
}
}
// Subroutine to compute Goertzel algorithm and return Real and Imag components for a single frequency bin
void GoertzelRealImag ( short intRealIn [ ] , int intPtr , int N , float m , float * dblReal , float * dblImag )
{
// intRealIn is a buffer at least intPtr + N in length
// N need not be a power of 2
// m need not be an integer
// Computes the Real and Imaginary Freq values for bin m
// Verified to = FFT results for at least 10 significant digits
// Timings for 1024 Point on Laptop (64 bit Core Duo 2.2 Ghz)
// GoertzelRealImag .015 ms Normal FFT (.5 ms)
// assuming Goertzel is proportional to N and FFT time proportional to Nlog2N
// FFT:Goertzel time ratio ~ 3.3 Log2(N)
// Sanity check
//if (intPtr < 0 Or (intRealIn.Length - intPtr) < N Then
// dblReal = 0 : dblImag = 0 : Exit Sub
// End If
float dblZ_1 = 0.0f , dblZ_2 = 0.0f , dblW = 0.0f ;
float dblCoeff = 2 * cosf ( 2 * M_PI * m / N ) ;
int i ;
for ( i = 0 ; i < = N ; i + + )
{
if ( i = = N )
dblW = dblZ_1 * dblCoeff - dblZ_2 ;
else
dblW = intRealIn [ intPtr ] + dblZ_1 * dblCoeff - dblZ_2 ;
dblZ_2 = dblZ_1 ;
dblZ_1 = dblW ;
intPtr + + ;
}
* dblReal = 2 * ( dblW - cosf ( 2 * M_PI * m / N ) * dblZ_2 ) / N ; // scale results by N/2
* dblImag = 2 * ( sinf ( 2 * M_PI * m / N ) * dblZ_2 ) / N ; // scale results by N/2 (this sign agrees with Scope DSP phase values)
}
// Subroutine to compute Goertzel algorithm and return Real and Imag components for a single frequency bin with a Hanning Window function
float dblHanWin [ 120 ] ;
float dblHanAng ;
int HanWinLen = 0 ;
float dblHannWin [ 480 ] ;
float dblHannAng ;
// Subroutine to compute Goertzel algorithm and return Real and Imag components for a single frequency bin with a Hann Window function for N a multiple of 120
void GoertzelRealImagHann120 ( short intRealIn [ ] , int intPtr , int N , float m , float * dblReal , float * dblImag )
{
// This version precomputes the raised cosine (Hann or Hanning) window and uses it for any length that is a multiple of 120 samples
// intRealIn is a buffer at least intPtr + N in length
// N must be 960 to use this function
// Hann coefficients are approximated for N>120 but should be close
// m need not be an integer
// Computes the Real and Imaginary Freq values for bin m
// Verified to = FFT results for at least 10 significant digits
// Timings for 1024 Point on Laptop (64 bit Core Duo 2.2 Ghz)
// GoertzelRealImag .015 ms Normal FFT (.5 ms)
// assuming Goertzel is proportional to N and FFT time proportional to Nlog2N
// FFT:Goertzel time ratio ~ 3.3 Log2(N)
float dblZ_1 = 0.0f , dblZ_2 = 0.0f , dblW = 0.0f ;
float dblCoeff = 2 * cosf ( 2 * M_PI * m / N ) ;
int i ;
int intM = N / 120 ; // No if 120 sample blocks
if ( HanWinLen ! = N ) //if there is any change in N this is then recalculate the Hanning Window...this mechanism reduces use of Cos
{
HanWinLen = N ;
dblHanAng = 2 * M_PI / 120 ;
for ( i = 0 ; i < 60 ; i + + )
{
dblHanWin [ i ] = 0.5 - 0.5 * cosf ( i * dblHanAng + dblHanAng ) ;
}
}
for ( i = 0 ; i < = N ; i + + )
{
if ( i = = N )
dblW = dblZ_1 * dblCoeff - dblZ_2 ;
else if ( i < ( N / 2 ) ) // ist half of 120 sample block
// looks like we use values 0 ti 59 then 59 down to 0
dblW = intRealIn [ intPtr ] * dblHanWin [ ( i / intM ) % 60 ] + dblZ_1 * dblCoeff - dblZ_2 ;
else
dblW = intRealIn [ intPtr ] * dblHanWin [ 59 - ( ( i / intM ) % 60 ) ] + dblZ_1 * dblCoeff - dblZ_2 ;
dblZ_2 = dblZ_1 ;
dblZ_1 = dblW ;
intPtr + + ;
}
* dblReal = 2 * ( dblW - cosf ( 2 * M_PI * m / N ) * dblZ_2 ) / N ; // scale results by N/2
* dblImag = 2 * ( sinf ( 2 * M_PI * m / N ) * dblZ_2 ) / N ; // scale results by N/2 (this sign agrees with Scope DSP phase values)
}
void GoertzelRealImagHann960 ( short intRealIn [ ] , int intPtr , int N , float m , float * dblReal , float * dblImag )
{
// This version precomputes the raised cosine (Hann or Hanning) window and uses it for any length that is a multiple of 120 samples
// intRealIn is a buffer at least intPtr + N in length
// N must be a multiple of 120 to use this function
// Hann coefficients are approximated for N>120 but should be close
// m need not be an integer
// Computes the Real and Imaginary Freq values for bin m
// Verified to = FFT results for at least 10 significant digits
// Timings for 1024 Point on Laptop (64 bit Core Duo 2.2 Ghz)
// GoertzelRealImag .015 ms Normal FFT (.5 ms)
// assuming Goertzel is proportional to N and FFT time proportional to Nlog2N
// FFT:Goertzel time ratio ~ 3.3 Log2(N)
float dblZ_1 = 0.0f , dblZ_2 = 0.0f , dblW = 0.0f ;
float dblCoeff = 2 * cosf ( 2 * M_PI * m / N ) ;
int i ;
int intM = N / 120 ; // No if 120 sample blocks
if ( dblHannWin [ 479 ] < 0.5 ) //if there is any change in N this is then recalculate the Hanning Window...this mechanism reduces use of Cos
{
dblHannAng = 2 * M_PI / 960 ;
for ( i = 0 ; i < 480 ; i + + )
{
dblHannWin [ i ] = 0.5 - 0.5 * cosf ( i * dblHannAng + dblHannAng ) ;
}
}
for ( i = 0 ; i < = N ; i + + )
{
if ( i = = N )
dblW = dblZ_1 * dblCoeff - dblZ_2 ;
else if ( i < ( N / 2 ) ) // ist half of 120 sample block
// looks like we use values 0 ti 59 then 59 down to 0
dblW = intRealIn [ intPtr ] * dblHannWin [ ( i / intM ) % 60 ] + dblZ_1 * dblCoeff - dblZ_2 ;
else
dblW = intRealIn [ intPtr ] * dblHannWin [ 479 - ( ( i / intM ) % 60 ) ] + dblZ_1 * dblCoeff - dblZ_2 ;
dblZ_2 = dblZ_1 ;
dblZ_1 = dblW ;
intPtr + + ;
}
* dblReal = 2 * ( dblW - cosf ( 2 * M_PI * m / N ) * dblZ_2 ) / N ; // scale results by N/2
* dblImag = 2 * ( sinf ( 2 * M_PI * m / N ) * dblZ_2 ) / N ; // scale results by N/2 (this sign agrees with Scope DSP phase values)
}
void GoertzelRealImagHanning ( short intRealIn [ ] , int intPtr , int N , float m , float * dblReal , float * dblImag )
{
// intRealIn is a buffer at least intPtr + N in length
// N need not be a power of 2
// m need not be an integer
// Computes the Real and Imaginary Freq values for bin m
// Verified to = FFT results for at least 10 significant digits
// Timings for 1024 Point on Laptop (64 bit Core Duo 2.2 Ghz)
// GoertzelRealImag .015 ms Normal FFT (.5 ms)
// assuming Goertzel is proportional to N and FFT time proportional to Nlog2N
// FFT:Goertzel time ratio ~ 3.3 Log2(N)
// Sanity check
float dblZ_1 = 0.0f , dblZ_2 = 0.0f , dblW = 0.0f ;
float dblCoeff = 2 * cosf ( 2 * M_PI * m / N ) ;
int i ;
if ( HanWinLen ! = N ) //if there is any change in N this is then recalculate the Hanning Window...this mechanism reduces use of Cos
{
HanWinLen = N ;
dblHanAng = 2 * M_PI / ( N - 1 ) ;
for ( i = 0 ; i < N ; i + + )
{
dblHanWin [ i ] = 0.5 - 0.5 * cosf ( i * dblHanAng ) ;
}
}
for ( i = 0 ; i < = N ; i + + )
{
if ( i = = N )
dblW = dblZ_1 * dblCoeff - dblZ_2 ;
else
dblW = intRealIn [ intPtr ] * dblHanWin [ i ] + dblZ_1 * dblCoeff - dblZ_2 ;
dblZ_2 = dblZ_1 ;
dblZ_1 = dblW ;
intPtr + + ;
}
* dblReal = 2 * ( dblW - cosf ( 2 * M_PI * m / N ) * dblZ_2 ) / N ; // scale results by N/2
* dblImag = 2 * ( sinf ( 2 * M_PI * m / N ) * dblZ_2 ) / N ; // scale results by N/2 (this sign agrees with Scope DSP phase values)
}
float dblHamWin [ 1200 ] ;
float dblHamAng ;
int HamWinLen = 0 ;
void GoertzelRealImagHamming ( short intRealIn [ ] , int intPtr , int N , float m , float * dblReal , float * dblImag )
{
// intRealIn is a buffer at least intPtr + N in length
// N need not be a power of 2
// m need not be an integer
// Computes the Real and Imaginary Freq values for bin m
// Verified to = FFT results for at least 10 significant digits
// Timings for 1024 Point on Laptop (64 bit Core Duo 2.2 Ghz)
// GoertzelRealImag .015 ms Normal FFT (.5 ms)
// assuming Goertzel is proportional to N and FFT time proportional to Nlog2N
// FFT:Goertzel time ratio ~ 3.3 Log2(N)
// Sanity check
float dblZ_1 = 0.0f , dblZ_2 = 0.0f , dblW = 0.0f ;
float dblCoeff = 2 * cosf ( 2 * M_PI * m / N ) ;
int i ;
if ( HamWinLen ! = N ) //if there is any cHamge in N this is then recalculate the Hanning Window...this mechanism reduces use of Cos
{
HamWinLen = N ;
dblHamAng = 2 * M_PI / ( N - 1 ) ;
for ( i = 0 ; i < N ; i + + )
{
dblHamWin [ i ] = 0.54f - 0.46f * cosf ( i * dblHamAng ) ;
}
}
for ( i = 0 ; i < = N ; i + + )
{
if ( i = = N )
dblW = dblZ_1 * dblCoeff - dblZ_2 ;
else
dblW = intRealIn [ intPtr ] * dblHamWin [ i ] + dblZ_1 * dblCoeff - dblZ_2 ;
dblZ_2 = dblZ_1 ;
dblZ_1 = dblW ;
intPtr + + ;
}
* dblReal = 2 * ( dblW - cosf ( 2 * M_PI * m / N ) * dblZ_2 ) / N ; // scale results by N/2
* dblImag = 2 * ( sinf ( 2 * M_PI * m / N ) * dblZ_2 ) / N ; // scale results by N/2 (this sign agrees with Scope DSP phase values)
}
// Function to interpolate spectrum peak using Quinn algorithm
float QuinnSpectralPeakLocator ( float XkM1Re , float XkM1Im , float XkRe , float XkIm , float XkP1Re , float XkP1Im )
{
// based on the Quinn algorithm in Streamlining Digital Processing page 139
// Alpha1 = Re(Xk-1/Xk)
// Alpha2 = Re(Xk+1/Xk)
//Delta1 = Alpha1/(1 - Alpha1)
//'Delta2 = Alpha2/(1 - Alpha2)
// if Delta1 > 0 and Delta2 > 0 then Delta = Delta2 else Delta = Delta1
// should be within .1 bin for S:N > 2 dB
float dblDenom = powf ( XkRe , 2 ) + powf ( XkIm , 2 ) ;
float dblAlpha1 ;
float dblAlpha2 ;
float dblDelta1 ;
float dblDelta2 ;
dblAlpha1 = ( ( XkM1Re * XkRe ) + ( XkM1Im * XkIm ) ) / dblDenom ;
dblAlpha2 = ( ( XkP1Re * XkRe ) + ( XkP1Im * XkIm ) ) / dblDenom ;
dblDelta1 = dblAlpha1 / ( 1 - dblAlpha1 ) ;
dblDelta2 = dblAlpha2 / ( 1 - dblAlpha2 ) ;
if ( dblDelta1 > 0 & & dblDelta2 > 0 )
return dblDelta2 ;
else
return dblDelta1 ;
}
// Function to interpolate spectrum peak using simple interpolation
float SpectralPeakLocator ( float XkM1Re , float XkM1Im , float XkRe , float XkIm , float XkP1Re , float XkP1Im , float * dblCentMag , char * Win )
{
// Use this for Windowed samples instead of QuinnSpectralPeakLocator
float dblLeftMag , dblRightMag ;
* dblCentMag = sqrtf ( powf ( XkRe , 2 ) + powf ( XkIm , 2 ) ) ;
dblLeftMag = sqrtf ( powf ( XkM1Re , 2 ) + powf ( XkM1Im , 2 ) ) ;
dblRightMag = sqrtf ( powf ( XkP1Re , 2 ) + powf ( XkP1Im , 2 ) ) ;
//Factor 1.22 empirically determine optimum for Hamming window
// For Hanning Window use factor of 1.36
// For Blackman Window use factor of 1.75
if ( strcmp ( Win , " Blackman " ) )
return 1.75 * ( dblRightMag - dblLeftMag ) / ( dblLeftMag + * dblCentMag + dblRightMag ) ; // Optimized for Hamming Window
if ( strcmp ( Win , " Hann " ) )
return 1.36 * ( dblRightMag - dblLeftMag ) / ( dblLeftMag + * dblCentMag + dblRightMag ) ; // Optimized for Hamming Window
if ( strcmp ( Win , " Hamming " ) )
return 1.22 * ( dblRightMag - dblLeftMag ) / ( dblLeftMag + * dblCentMag + dblRightMag ) ; // Optimized for Hamming Window
return 0 ;
}
// Function to detect and tune the 50 baud 2 tone leader (for all bandwidths) Updated version of SearchFor2ToneLeader2
float dblPriorFineOffset = 1000.0f ;
BOOL SearchFor2ToneLeader3 ( short * intNewSamples , int Length , float * dblOffsetHz , int * intSN )
{
// This version uses 10Hz bin spacing. Hamming window on Goertzel, and simple spectral peak interpolator
// It requires about 50% more CPU time when running but produces more sensive leader detection and more accurate tuning
// search through the samples looking for the telltail 50 baud 2 tone pattern (nominal tones 1475, 1525 Hz)
// Find the offset in Hz (due to missmatch in transmitter - receiver tuning
// Finds the S:N (power ratio of the tones 1475 and 1525 ratioed to "noise" averaged from bins at 1425, 1450, 1550, and 1575Hz)
float dblGoertzelReal [ 56 ] ;
float dblGoertzelImag [ 56 ] ;
float dblMag [ 56 ] ;
float dblPower , dblLeftMag , dblRightMag ;
float dblMaxPeak = 0.0 , dblMaxPeakSN = 0.0 , dblBinAdj ;
int intInterpCnt = 0 ; // the count 0 to 3 of the interpolations that were < +/- .5 bin
int intIatMaxPeak = 0 ;
float dblAlpha = 0.3f ; // Works well possibly some room for optimization Changed from .5 to .3 on Rev 0.1.5.3
float dblInterpretThreshold = 1.0f ; // Good results June 6, 2014 (was .4) ' Works well possibly some room for optimization
float dblFilteredMaxPeak = 0 ;
int intStartBin , intStopBin ;
float dblLeftCar , dblRightCar , dblBinInterpLeft , dblBinInterpRight , dblCtrR , dblCtrI , dblLeftP , dblRightP ;
float dblLeftR [ 3 ] , dblLeftI [ 3 ] , dblRightR [ 3 ] , dblRightI [ 3 ] ;
int i ;
int Ptr = 0 ;
float dblAvgNoisePerBin , dblCoarsePwrSN , dblBinAdj1475 , dblBinAdj1525 , dblCoarseOffset = 1000 ;
float dblTrialOffset , dblPowerEarly , dblSNdBPwrEarly ;
if ( ( Length ) < 1200 )
return FALSE ; // ensure there are at least 1200 samples (5 symbols of 240 samples)
// if ((Now - dttLastGoodFrameTypeDecode > 20000) && TuningRange > 0)
{
// this is the full search over the full tuning range selected. Uses more CPU time and with possibly larger deviation once connected.
intStartBin = ( ( 200 - TuningRange ) / 10 ) ;
intStopBin = 55 - intStartBin ;
dblMaxPeak = 0 ;
// Generate the Power magnitudes for up to 56 10 Hz bins (a function of MCB.TuningRange)
for ( i = intStartBin ; i < = intStopBin ; i + + )
{
// note hamming window reduces end effect caused by 1200 samples (not an even multiple of 240) but spreads response peaks
GoertzelRealImagHamming ( intNewSamples , Ptr , 1200 , i + 122.5f , & dblGoertzelReal [ i ] , & dblGoertzelImag [ i ] ) ;
dblMag [ i ] = powf ( dblGoertzelReal [ i ] , 2 ) + powf ( dblGoertzelImag [ i ] , 2 ) ; // dblMag(i) in units of power (V^2)
}
// Search the bins to locate the max S:N in the two tone signal/avg noise.
for ( i = intStartBin + 5 ; i < = intStopBin - 10 ; i + + ) // ' +/- MCB.TuningRange from nominal
{
dblPower = sqrtf ( dblMag [ i ] * dblMag [ i + 5 ] ) ; // using the product to minimize sensitivity to one strong carrier vs the two tone
// sqrt converts back to units of power from Power ^2
// don't use center noise bin as too easily corrupted by adjacent carriers
dblAvgNoisePerBin = ( dblMag [ i - 5 ] + dblMag [ i - 3 ] + dblMag [ i + 8 ] + dblMag [ i + 10 ] ) / 4 ; // Simple average
dblMaxPeak = dblPower / dblAvgNoisePerBin ;
if ( dblMaxPeak > dblMaxPeakSN )
{
dblMaxPeakSN = dblMaxPeak ;
dblCoarsePwrSN = 10 * log10f ( dblMaxPeak ) ;
intIatMaxPeak = i + 122 ;
}
}
// Do the interpolation based on the two carriers at nominal 1475 and 1525Hz
if ( ( ( intIatMaxPeak - 123 ) > = intStartBin ) & & ( ( intIatMaxPeak - 118 ) < = intStopBin ) ) // check to ensure no index errors
{
// Interpolate the adjacent bins using QuinnSpectralPeakLocator
dblBinAdj1475 = SpectralPeakLocator (
dblGoertzelReal [ intIatMaxPeak - 123 ] , dblGoertzelImag [ intIatMaxPeak - 123 ] ,
dblGoertzelReal [ intIatMaxPeak - 122 ] , dblGoertzelImag [ intIatMaxPeak - 122 ] ,
dblGoertzelReal [ intIatMaxPeak - 121 ] , dblGoertzelImag [ intIatMaxPeak - 121 ] , & dblLeftMag , " Hamming " ) ;
if ( dblBinAdj1475 < dblInterpretThreshold & & dblBinAdj1475 > - dblInterpretThreshold )
{
dblBinAdj = dblBinAdj1475 ;
intInterpCnt + = 1 ;
}
dblBinAdj1525 = SpectralPeakLocator (
dblGoertzelReal [ intIatMaxPeak - 118 ] , dblGoertzelImag [ intIatMaxPeak - 118 ] ,
dblGoertzelReal [ intIatMaxPeak - 117 ] , dblGoertzelImag [ intIatMaxPeak - 117 ] ,
dblGoertzelReal [ intIatMaxPeak - 116 ] , dblGoertzelImag [ intIatMaxPeak - 116 ] , & dblRightMag , " Hamming " ) ;
if ( dblBinAdj1525 < dblInterpretThreshold & & dblBinAdj1525 > - dblInterpretThreshold )
{
dblBinAdj + = dblBinAdj1525 ;
intInterpCnt + = 1 ;
}
if ( intInterpCnt = = 0 )
{
dblPriorFineOffset = 1000.0f ;
return FALSE ;
}
else
{
dblBinAdj = dblBinAdj / intInterpCnt ; // average the offsets that are within 1 bin
dblCoarseOffset = 10.0f * ( intIatMaxPeak + dblBinAdj - 147 ) ; // compute the Coarse tuning offset in Hz
}
}
else
{
dblPriorFineOffset = 1000.0f ;
return FALSE ;
}
}
// Drop into Narrow Search
if ( dblCoarseOffset < 999 )
dblTrialOffset = dblCoarseOffset ; // use the CoarseOffset calculation from above
else
dblTrialOffset = * dblOffsetHz ; // use the prior offset value
if ( fabsf ( dblTrialOffset ) > TuningRange & & TuningRange > 0 )
{
dblPriorFineOffset = 1000.0f ;
return False ;
}
dblLeftCar = 147.5f + dblTrialOffset / 10.0f ; // the nominal positions of the two tone carriers based on the last computerd dblOffsetHz
dblRightCar = 152.5f + dblTrialOffset / 10.0f ;
// Calculate 4 bins total for Noise values in S/N computation (calculate average noise) ' Simple average of noise bins
GoertzelRealImagHamming ( intNewSamples , Ptr , 1200 , 142.5f + dblTrialOffset / 10.0f , & dblCtrR , & dblCtrI ) ; // nominal center -75 Hz
dblAvgNoisePerBin = powf ( dblCtrR , 2 ) + powf ( dblCtrI , 2 ) ;
GoertzelRealImagHamming ( intNewSamples , Ptr , 1200 , 145.0f + dblTrialOffset / 10.0f , & dblCtrR , & dblCtrI ) ; // center - 50 Hz
dblAvgNoisePerBin + = powf ( dblCtrR , 2 ) + powf ( dblCtrI , 2 ) ;
GoertzelRealImagHamming ( intNewSamples , Ptr , 1200 , 155.0 + dblTrialOffset / 10.0f , & dblCtrR , & dblCtrI ) ; // center + 50 Hz
dblAvgNoisePerBin + = powf ( dblCtrR , 2 ) + powf ( dblCtrI , 2 ) ;
GoertzelRealImagHamming ( intNewSamples , Ptr , 1200 , 157.5 + dblTrialOffset / 10.0f , & dblCtrR , & dblCtrI ) ; // center + 75 Hz
dblAvgNoisePerBin + = powf ( dblCtrR , 2 ) + powf ( dblCtrI , 2 ) ;
dblAvgNoisePerBin = dblAvgNoisePerBin * 0.25f ; // simple average, now units of power
// Calculate one bin above and below the two nominal 2 tone positions for Quinn Spectral Peak locator
GoertzelRealImagHamming ( intNewSamples , Ptr , 1200 , dblLeftCar - 1 , & dblLeftR [ 0 ] , & dblLeftI [ 0 ] ) ;
GoertzelRealImagHamming ( intNewSamples , Ptr , 1200 , dblLeftCar , & dblLeftR [ 1 ] , & dblLeftI [ 1 ] ) ;
dblLeftP = powf ( dblLeftR [ 1 ] , 2 ) + powf ( dblLeftI [ 1 ] , 2 ) ;
GoertzelRealImagHamming ( intNewSamples , Ptr , 1200 , dblLeftCar + 1 , & dblLeftR [ 2 ] , & dblLeftI [ 2 ] ) ;
GoertzelRealImagHamming ( intNewSamples , Ptr , 1200 , dblRightCar - 1 , & dblRightR [ 0 ] , & dblRightI [ 0 ] ) ;
GoertzelRealImagHamming ( intNewSamples , Ptr , 1200 , dblRightCar , & dblRightR [ 1 ] , & dblRightI [ 1 ] ) ;
dblRightP = powf ( dblRightR [ 1 ] , 2 ) + powf ( dblRightI [ 1 ] , 2 ) ;
GoertzelRealImag ( intNewSamples , Ptr , 1200 , dblRightCar + 1 , & dblRightR [ 2 ] , & dblRightI [ 2 ] ) ;
// Calculate the total power in the two tones
// This mechanism designed to reject single carrier but average both carriers if ratios is less than 4:1
if ( dblLeftP > 4 * dblRightP )
dblPower = dblRightP ;
else if ( dblRightP > 4 * dblLeftP )
dblPower = dblLeftP ;
else
dblPower = sqrtf ( dblLeftP * dblRightP ) ;
dblSNdBPwr = 10 * log10f ( dblPower / dblAvgNoisePerBin ) ;
// Early leader detect code to calculate S:N on the first 2 symbols)
// concept is to allow more accurate framing and sync detection and reduce false leader detects
GoertzelRealImag ( intNewSamples , Ptr , 480 , 57.0f + dblTrialOffset / 25.0f , & dblCtrR , & dblCtrI ) ; // nominal center -75 Hz
dblAvgNoisePerBin = powf ( dblCtrR , 2 ) + powf ( dblCtrI , 2 ) ;
GoertzelRealImag ( intNewSamples , Ptr , 480 , 58.0f + dblTrialOffset / 25.0f , & dblCtrR , & dblCtrI ) ; // nominal center -75 Hz
dblAvgNoisePerBin + = powf ( dblCtrR , 2 ) + powf ( dblCtrI , 2 ) ;
GoertzelRealImag ( intNewSamples , Ptr , 480 , 62.0f + dblTrialOffset / 25.0f , & dblCtrR , & dblCtrI ) ; // nominal center -75 Hz
dblAvgNoisePerBin + = powf ( dblCtrR , 2 ) + powf ( dblCtrI , 2 ) ;
GoertzelRealImag ( intNewSamples , Ptr , 480 , 63.0f + dblTrialOffset / 25.0f , & dblCtrR , & dblCtrI ) ; // nominal center -75 Hz
dblAvgNoisePerBin = max ( 1000.0f , 0.25 * ( dblAvgNoisePerBin + powf ( dblCtrR , 2 ) + powf ( dblCtrI , 2 ) ) ) ; // average of 4 noise bins
dblLeftCar = 59 + dblTrialOffset / 25 ; // the nominal positions of the two tone carriers based on the last computerd dblOffsetHz
dblRightCar = 61 + dblTrialOffset / 25 ;
GoertzelRealImag ( intNewSamples , Ptr , 480 , dblLeftCar , & dblCtrR , & dblCtrI ) ; // LEFT carrier
dblLeftP = powf ( dblCtrR , 2 ) + powf ( dblCtrI , 2 ) ;
GoertzelRealImag ( intNewSamples , Ptr , 480 , dblRightCar , & dblCtrR , & dblCtrI ) ; // Right carrier
dblRightP = powf ( dblCtrR , 2 ) + powf ( dblCtrI , 2 ) ;
// the following rejects a single tone carrier but averages the two tones if ratio is < 4:1
if ( dblLeftP > 4 * dblRightP )
dblPowerEarly = dblRightP ;
else if ( dblRightP > 4 * dblLeftP )
dblPowerEarly = dblLeftP ;
else
dblPowerEarly = sqrtf ( dblLeftP * dblRightP ) ;
dblSNdBPwrEarly = 10 * log10f ( dblPowerEarly / dblAvgNoisePerBin ) ;
// End of Early leader detect test code
if ( dblSNdBPwr > ( 4 + Squelch ) & & dblSNdBPwrEarly > Squelch & & ( dblAvgNoisePerBin > 100.0f | | dblPriorFineOffset ! = 1000.0f ) ) // making early threshold = lower (after 3 dB compensation for bandwidth)
{
// Debugprintf("Fine Search S:N= %f dB, Early S:N= %f dblAvgNoisePerBin %f ", dblSNdBPwr, dblSNdBPwrEarly, dblAvgNoisePerBin);
// Calculate the interpolation based on the left of the two tones
dblBinInterpLeft = SpectralPeakLocator ( dblLeftR [ 0 ] , dblLeftI [ 0 ] , dblLeftR [ 1 ] , dblLeftI [ 1 ] , dblLeftR [ 2 ] , dblLeftI [ 2 ] , & dblLeftMag , " Hamming " ) ;
// And the right of the two tones
dblBinInterpRight = SpectralPeakLocator ( dblRightR [ 0 ] , dblRightI [ 0 ] , dblRightR [ 1 ] , dblRightI [ 1 ] , dblRightR [ 2 ] , dblRightI [ 2 ] , & dblRightMag , " Hamming " ) ;
// Weight the interpolated values in proportion to their magnitudes
dblBinInterpLeft = dblBinInterpLeft * dblLeftMag / ( dblLeftMag + dblRightMag ) ;
dblBinInterpRight = dblBinInterpRight * dblRightMag / ( dblLeftMag + dblRightMag ) ;
# ifdef ARMLINUX
{
int x = round ( dblBinInterpLeft ) ; // odd, but PI doesnt print floats properly
int y = round ( dblBinInterpRight ) ;
// Debugprintf(" SPL Left= %d SPL Right= %d Offset %f, LeftMag %f RightMag %f", x, y, *dblOffsetHz, dblLeftMag, dblRightMag);
}
# else
// Debugprintf(" SPL Left= %f SPL Right= %f, Offset %f, LeftMag %f RightMag %f",
// dblBinInterpLeft, dblBinInterpRight, *dblOffsetHz, dblLeftMag, dblRightMag);
# endif
if ( fabsf ( dblBinInterpLeft + dblBinInterpRight ) < 1.0 ) // sanity check for the interpolators
{
if ( dblBinInterpLeft + dblBinInterpRight > 0 ) // consider different bounding below
* dblOffsetHz = dblTrialOffset + min ( ( dblBinInterpLeft + dblBinInterpRight ) * 10.0f , 3 ) ; // average left and right, adjustment bounded to +/- 3Hz max
else
* dblOffsetHz = dblTrialOffset + max ( ( dblBinInterpLeft + dblBinInterpRight ) * 10.0f , - 3 ) ;
// Note the addition of requiring a second detect with small offset dramatically reduces false triggering even at Squelch values of 3
// The following demonstrated good detection down to -10 dB S:N with squelch = 3 and minimal false triggering.
// Added rev 0.8.2.2 11/6/2016 RM
if ( abs ( dblPriorFineOffset - * dblOffsetHz ) < 2.9f )
{
Debugprintf ( " Prior-Offset= %f " , ( dblPriorFineOffset - * dblOffsetHz ) ) ;
// Capture power for debugging ...note: convert to 3 KHz noise bandwidth from 25Hz or 12.Hz for reporting consistancy.
Debugprintf ( " Ldr; S:N(3KHz) Early= %f dB, Full %f dB, Offset= %f Hz: " , dblSNdBPwrEarly - 20.8f , dblSNdBPwr - 24.77f , * dblOffsetHz ) ;
dttStartRmtLeaderMeasure = Now ;
if ( AccumulateStats )
{
dblLeaderSNAvg = ( ( dblLeaderSNAvg * intLeaderDetects ) + dblSNdBPwr ) / ( 1 + intLeaderDetects ) ;
intLeaderDetects + + ;
}
dblNCOFreq = 3000 + * dblOffsetHz ; // Set the NCO frequency and phase inc for mixing
dblNCOPhaseInc = dbl2Pi * dblNCOFreq / 12000 ;
dttLastLeaderDetect = dttStartRmtLeaderMeasure = Now ;
State = AcquireSymbolSync ;
* intSN = dblSNdBPwr - 24.77 ; // 23.8dB accomodates ratio of 3Kz BW:10 Hz BW (10Log 3000/10 = 24.77)
// don't advance the pointer here
dblPriorFineOffset = 1000.0f ;
return TRUE ;
}
else
dblPriorFineOffset = * dblOffsetHz ;
// always use 1 symbol inc when looking for next minimal offset
}
}
return FALSE ;
}
BOOL SearchFor2ToneLeader4 ( short * intNewSamples , int Length , float * dblOffsetHz , int * intSN )
{
// This version uses 12.5 Hz bin spacing. Blackman window on Goertzel, and simple spectral peak interpolator optimized for Blackman
// Blackman selected for maximum rejection (about 60 dB) of the other two-tone bin 50 Hz (4 x 12.5 Hz bins) away.
// search through the samples looking for the telltail 50 baud 2 tone pattern (nominal tones 1475, 1525 Hz)
// Find the offset in Hz (due to missmatch in transmitter - receiver tuning
// Finds the S:N (power ratio of the tones 1475 and 1525 ratioed to "noise" averaged from bins at 1425, 1450, 1550, and 1575Hz)
float dblGoertzelReal [ 45 ] ;
float dblGoertzelImag [ 45 ] ;
float dblMag [ 45 ] ;
float dblPower , dblPwrSNdB , dblLeftMag , dblRightMag , dblAvgNoisePerBinAtPeak ;
float dblRealL , dblRealR , dblImagL , dblImagR ;
float dblMaxPeak = 0.0 , dblMaxPeakSN = 0.0 , dblMagWindow ;
int intInterpCnt = 0 ; // the count 0 to 3 of the interpolations that were < +/- .5 bin
int intIatMaxPeak = 0 ;
float dblAlpha = 0.3f ; // Works well possibly some room for optimization Changed from .5 to .3 on Rev 0.1.5.3
float dblInterpretThreshold = 1.0f ; // Good results June 6, 2014 (was .4) ' Works well possibly some room for optimization
float dblFilteredMaxPeak = 0 ;
int intStartBin , intStopBin ;
int i ;
int Ptr = 0 ;
float dblAvgNoisePerBin , dblBinAdj1475 , dblBinAdj1525 , dblCoarseOffset = 1000 ;
float dblOffset = 1000 ; // initialize to impossible value
// This should allow tunning from nominal bins at 1425Hz to 1575Hz +/- 200 Hz tuning range
if ( ( Length ) < 1200 )
return FALSE ; // ensure there are at least 1200 samples (5 symbols of 240 samples)
// if ((Now - dttLastGoodFrameTypeDecode > 20000) && TuningRange > 0)
// {
// // this is the full search over the full tuning range selected. Uses more CPU time and with possibly larger deviation once connected.
intStartBin = ( ( 200 - TuningRange ) / 12.5 ) ;
intStopBin = 44 - intStartBin ;
dblMaxPeak = 0 ;
dblMagWindow = 0 ;
dblMaxPeakSN = - 100 ;
// Generate the Power magnitudes for up to 56 10 Hz bins (a function of MCB.TuningRange)
for ( i = intStartBin ; i < = intStopBin ; i + + )
{
// note Blackman window reduced end effect but looses sensitivity so sticking with Hann window
// Test of 4/22/2018 indicated accurate Hann window (960) gives about 1-2 dB more sensitivity than Blackman window
GoertzelRealImagHann960 ( intNewSamples , Ptr , 960 , i + 98 , & dblGoertzelReal [ i ] , & dblGoertzelImag [ i ] ) ;
dblMag [ i ] = powf ( dblGoertzelReal [ i ] , 2 ) + powf ( dblGoertzelImag [ i ] , 2 ) ; // dblMag(i) in units of power (V^2)
dblMagWindow + = dblMag [ i ] ;
}
// Search the bins to locate the max S:N in the two tone signal/avg noise.
for ( i = intStartBin + 4 ; i < = intStopBin - 8 ; i + + ) // ' +/- MCB.TuningRange from nominal
{
dblPower = sqrtf ( dblMag [ i ] * dblMag [ i + 4 ] ) ; // using the product to minimize sensitivity to one strong carrier vs the two tone
// sqrt converts back to units of power from Power ^2
// don't use center 7 noise bins as too easily corrupted by adjacent two-tone carriers
dblAvgNoisePerBin = ( dblMagWindow - ( dblMag [ i - 1 ] + dblMag [ i ] + dblMag [ i + 1 ] + dblMag [ i + 2 ] + dblMag [ i + 3 ] + dblMag [ i + 4 ] + dblMag [ i + 5 ] ) ) / ( intStopBin - ( intStartBin + 7 ) ) ;
dblMaxPeak = dblPower / dblAvgNoisePerBin ;
if ( dblMaxPeak > dblMaxPeakSN )
{
dblMaxPeakSN = dblMaxPeak ;
dblAvgNoisePerBinAtPeak = max ( dblAvgNoisePerBin , 1000.0f ) ;
intIatMaxPeak = i + 98 ;
}
}
dblMaxPeakSN = ( dblMag [ intIatMaxPeak - 98 ] + dblMag [ intIatMaxPeak - 94 ] ) / dblAvgNoisePerBinAtPeak ;
dblPwrSNdB = 10.0f * log10f ( dblMaxPeakSN ) ;
// Check aquelch
if ( ( dblPwrSNdB > ( 3 * Squelch ) ) & & dblPwrSNPower_dBPrior > ( 3 * Squelch ) )
{
// Do the interpolation based on the two carriers at nominal 1475 and 1525Hz
if ( ( ( intIatMaxPeak - 99 ) > = intStartBin ) & & ( ( intIatMaxPeak - 103 ) < = intStopBin ) ) // check to ensure no index errors
{
// Interpolate the adjacent bins using QuinnSpectralPeakLocator
dblBinAdj1475 = SpectralPeakLocator (
dblGoertzelReal [ intIatMaxPeak - 99 ] , dblGoertzelImag [ intIatMaxPeak - 99 ] ,
dblGoertzelReal [ intIatMaxPeak - 98 ] , dblGoertzelImag [ intIatMaxPeak - 98 ] ,
dblGoertzelReal [ intIatMaxPeak - 97 ] , dblGoertzelImag [ intIatMaxPeak - 97 ] , & dblLeftMag , " Hann " ) ;
dblBinAdj1525 = SpectralPeakLocator (
dblGoertzelReal [ intIatMaxPeak - 95 ] , dblGoertzelImag [ intIatMaxPeak - 95 ] ,
dblGoertzelReal [ intIatMaxPeak - 94 ] , dblGoertzelImag [ intIatMaxPeak - 94 ] ,
dblGoertzelReal [ intIatMaxPeak - 93 ] , dblGoertzelImag [ intIatMaxPeak - 93 ] , & dblRightMag , " Hann " ) ;
// Weight the offset calculation by the magnitude of the dblLeftMag and dblRightMag carriers
dblOffset = 12.5 * ( intIatMaxPeak + dblBinAdj1475 * dblLeftMag / ( dblLeftMag + dblRightMag ) + dblBinAdj1525 * dblRightMag / ( dblLeftMag + dblRightMag ) - 118 ) ; // compute the Coarse tuning offset in Hz
if ( fabsf ( dblOffset ) > ( TuningRange + 7 ) ) // Was 7 caused tuning problems
{
dblPwrSNPower_dBPrior = dblPwrSNdB ;
return False ;
}
// recompute the S:N based on the interpolated bins and average with computation 1 and 2 symbols in the future
// Use of Hann window increases sensitivity slightly (1-2 dB)
GoertzelRealImagHann120 ( intNewSamples , 0 , 960 , intIatMaxPeak + dblOffset / 12.5 , & dblRealL , & dblImagL ) ;
GoertzelRealImagHann120 ( intNewSamples , 0 , 960 , intIatMaxPeak + 4 + dblOffset / 12.5 , & dblRealR , & dblImagR ) ;
dblMaxPeakSN = ( powf ( dblRealL , 2 ) + powf ( dblImagL , 2 ) + powf ( dblRealR , 2 ) + powf ( dblImagR , 2 ) ) / dblAvgNoisePerBinAtPeak ;
// now compute for 120 samples later
GoertzelRealImagHann120 ( intNewSamples , 120 , 960 , intIatMaxPeak + dblOffset / 12.5 , & dblRealL , & dblImagL ) ;
GoertzelRealImagHann120 ( intNewSamples , 120 , 960 , intIatMaxPeak + 4 + dblOffset / 12.5 , & dblRealR , & dblImagR ) ;
dblMaxPeakSN + = ( powf ( dblRealL , 2 ) + powf ( dblImagL , 2 ) + powf ( dblRealR , 2 ) + powf ( dblImagR , 2 ) ) / dblAvgNoisePerBinAtPeak ;
// and a third 240 samples later
GoertzelRealImagHann120 ( intNewSamples , 240 , 960 , intIatMaxPeak + dblOffset / 12.5 , & dblRealL , & dblImagL ) ;
GoertzelRealImagHann120 ( intNewSamples , 240 , 960 , intIatMaxPeak + 4 + dblOffset / 12.5 , & dblRealR , & dblImagR ) ;
dblMaxPeakSN + = ( powf ( dblRealL , 2 ) + powf ( dblImagL , 2 ) + powf ( dblRealR , 2 ) + powf ( dblImagR , 2 ) ) / dblAvgNoisePerBinAtPeak ;
dblMaxPeakSN = dblMaxPeakSN / 3 ; // average the dblMaxPeakSN over the three calculations
// ???? Calc Twice ????
dblMaxPeakSN = ( powf ( dblRealL , 2 ) + powf ( dblImagL , 2 ) + powf ( dblRealR , 2 ) + powf ( dblImagR , 2 ) ) / dblAvgNoisePerBinAtPeak ;
dblPwrSNdB = 10 * log10f ( dblMaxPeakSN ) ;
if ( dblPwrSNdB > 3 * Squelch ) // This average power now includes two samples from symbols +120 and + 240 samples
{
//strDecodeCapture = "Ldr; S:N(3KHz) Prior=" & Format(dblPwrSNPower_dBPrior, "#.0") & "dB, Current=" & Format(dblPwrSNdB, "#.0") & "dB, Offset=" & Format(dblOffset, "##0.00") & "Hz "
Debugprintf ( " Ldr; S:N(3KHz) Avg= %f dB, Offset== %f Hz " , dblPwrSNdB , dblOffset ) ;
dttStartRmtLeaderMeasure = Now ;
if ( AccumulateStats )
{
dblLeaderSNAvg = ( ( dblLeaderSNAvg * intLeaderDetects ) + dblPwrSNdB ) / ( 1 + intLeaderDetects ) ;
intLeaderDetects + = 1 ;
}
* dblOffsetHz = dblOffset ;
dblNCOFreq = 3000 + * dblOffsetHz ; // Set the NCO frequency and phase inc for mixing
dblNCOPhaseInc = dbl2Pi * dblNCOFreq / 12000 ;
// don't advance the pointer here
State = AcquireSymbolSync ;
dttLastLeaderDetect = Now ;
dblPhaseDiff1_2Avg = 10 ; // initialize to 10 to cause initialization of exponential averager in AcquireFrameSyncRSBAvg
* intSN = round ( dblPwrSNdB - 20.8 ) ; // 20.8dB accomodates ratio of 3Kz BW: (effective Blackman Window bandwidth of ~25 Hz)
return True ;
}
else
{
return False ;
}
}
}
dblPwrSNPower_dBPrior = dblPwrSNdB ;
return FALSE ;
}
// Function to look at the 2 tone leader and establishes the Symbol framing using envelope search and minimal phase error.
BOOL Acquire2ToneLeaderSymbolFraming ( )
{
float dblCarPh ;
float dblReal , dblImag ;
int intLocalPtr = intMFSReadPtr ; // try advancing one symbol to minimize initial startup errors
float dblAbsPhErr ;
float dblMinAbsPhErr = 5000 ; // initialize to an excessive value
int intIatMinErr ;
float dblPhaseAtMinErr ;
int intAbsPeak = 0 ;
int intJatPeak = 0 ;
int i ;
// Use Phase of 1500 Hz leader to establish symbol framing. Nominal phase is 0 or 180 degrees
if ( ( intFilteredMixedSamplesLength - intLocalPtr ) < 960 )
return FALSE ; // not enough
intLocalPtr = intMFSReadPtr + EnvelopeCorrelatorNew ( ) ; // should position the pointer at the symbol boundary
if ( intLocalPtr < intMFSReadPtr )
return False ; // use negative value of EnvelopeCorrelator to indicate insufficient correlation.
// Check 2 samples either side of the intLocalPtr for minimum phase error.(closest to Pi or -Pi)
// Could be as much as .4 Radians (~70 degrees) depending on sampling positions.
for ( i = - 2 ; i < = 2 ; i + + ) // 0 To 0 ' -2 To 2 ' for just 5 samples
{
// using the full symbol seemed to work best on weak Signals (0 to -5 dB S/N) June 15, 2015
GoertzelRealImagHann120 ( intFilteredMixedSamples , intLocalPtr + i , 240 , 30 , & dblReal , & dblImag ) ; // Carrier at 1500 Hz nominal Positioning
dblCarPh = atan2f ( dblImag , dblReal ) ;
dblAbsPhErr = fabsf ( dblCarPh - ( ceil ( dblCarPh / M_PI ) * M_PI ) ) ;
if ( dblAbsPhErr < dblMinAbsPhErr )
{
dblMinAbsPhErr = dblAbsPhErr ;
intIatMinErr = i ;
dblPhaseAtMinErr = dblCarPh ;
}
}
intMFSReadPtr = intLocalPtr + intIatMinErr ;
Debugprintf ( " [Acquire2ToneLeaderSymbolFraming] intIatMinError= %d, Leader Length %d mS " , intIatMinErr , Now - dttLastLeaderDetect ) ;
State = AcquireFrameSync ;
if ( AccumulateStats )
intLeaderSyncs + + ;
//Debug.WriteLine(" [Acquire2ToneLeaderSymbolSync] iAtMinError = " & intIatMinErr.ToString & " Ptr = " & intMFSReadPtr.ToString & " MinAbsPhErr = " & Format(dblMinAbsPhErr, "#.00"))
//Debug.WriteLine(" [Acquire2ToneLeaderSymbolSync] Ph1500 @ MinErr = " & Format(dblPhaseAtMinErr, "#.000"))
//strDecodeCapture &= "Framing; iAtMinErr=" & intIatMinErr.ToString & ", Ptr=" & intMFSReadPtr.ToString & ", MinAbsPhErr=" & Format(dblMinAbsPhErr, "#.00") & ": "
intPhaseError = 0 ;
return TRUE ;
}
// Function to establish symbol sync
int EnvelopeCorrelator ( )
{
// Compute the two symbol correlation with the Two tone leader template.
// slide the correlation one sample and repeat up to 240 steps
// keep the point of maximum or minimum correlation...and use this to identify the the symbol start.
float dblCorMax = - 1000000.0f ; // Preset to excessive values
float dblCorMin = 1000000.0f ;
int intJatMax = 0 , intJatMin = 0 ;
float dblCorSum , dblCorProduct , dblCorMaxProduct = 0.0 ;
int i , j ;
short int75HzFiltered [ 720 ] ;
if ( intFilteredMixedSamplesLength < intMFSReadPtr + 720 )
return - 1 ;
Filter75Hz ( int75HzFiltered , TRUE , 720 ) ; // This filter appears to help reduce avg decode distance (10 frames) by about 14%-19% at WGN-5 May 3, 2015
for ( j = 0 ; j < 360 ; j + + ) // Over 1.5 symbols
{
dblCorSum = 0 ;
for ( i = 0 ; i < 240 ; i + + ) // over 1 50 baud symbol (may be able to reduce to 1 symbol)
{
dblCorProduct = int50BaudTwoToneLeaderTemplate [ i ] * int75HzFiltered [ 120 + i + j ] ; // note 120 accomdates filter delay of 120 samples
dblCorSum + = dblCorProduct ;
if ( fabsf ( dblCorProduct ) > dblCorMaxProduct )
dblCorMaxProduct = fabsf ( dblCorProduct ) ;
}
if ( fabsf ( dblCorSum ) > dblCorMax )
{
dblCorMax = fabsf ( dblCorSum ) ;
intJatMax = j ;
}
}
if ( AccumulateStats )
{
dblAvgCorMaxToMaxProduct = ( dblAvgCorMaxToMaxProduct * intEnvelopeCors + ( dblCorMax / dblCorMaxProduct ) ) / ( intEnvelopeCors + 1 ) ;
intEnvelopeCors + + ;
}
// if (dblCorMax > 40 * dblCorMaxProduct)
{
Debugprintf ( " EnvelopeCorrelator CorMax:MaxProd= %f J= %d " , dblCorMax / dblCorMaxProduct , intJatMax ) ;
return intJatMax ;
}
// else
// return -1;
}
int EnvelopeCorrelatorNew ( )
{
// Compute the two symbol correlation with the Two tone leader template.
// slide the correlation one sample and repeat up to 240 steps
// keep the point of maximum or minimum correlation...and use this to identify the the symbol start.
float dblCorMax = - 1000000.0f ; // Preset to excessive values
float dblCorMin = 1000000.0f ;
int intJatMax = 0 , intJatMin = 0 ;
float dblCorSum , dblCorProduct , dblCorMaxProduct = 0.0 ;
int i , j ;
short int75HzFiltered [ 960 ] ;
if ( intFilteredMixedSamplesLength < intMFSReadPtr + 960 )
return - 1 ;
Filter75Hz ( int75HzFiltered , TRUE , 960 ) ; // This filter appears to help reduce avg decode distance (10 frames) by about 14%-19% at WGN-5 May 3, 2015
for ( j = 360 ; j < 600 ; j + + ) // Over 2 symbols
{
dblCorSum = 0 ;
for ( i = 0 ; i < 240 ; i + + ) // over 1 50 baud symbol (may be able to reduce to 1 symbol)
{
dblCorProduct = int50BaudTwoToneLeaderTemplate [ i ] * int75HzFiltered [ 120 + i + j ] ; // note 120 accomdates filter delay of 120 samples
dblCorSum + = dblCorProduct ;
if ( fabsf ( dblCorProduct ) > dblCorMaxProduct )
dblCorMaxProduct = fabsf ( dblCorProduct ) ;
}
if ( fabsf ( dblCorSum ) > dblCorMax )
{
dblCorMax = fabsf ( dblCorSum ) ;
intJatMax = j ;
}
}
if ( AccumulateStats )
{
dblAvgCorMaxToMaxProduct = ( dblAvgCorMaxToMaxProduct * intEnvelopeCors + ( dblCorMax / dblCorMaxProduct ) ) / ( intEnvelopeCors + 1 ) ;
intEnvelopeCors + + ;
}
if ( dblCorMax > 40 * dblCorMaxProduct )
{
Debugprintf ( " EnvelopeCorrelator CorMax:MaxProd= %f J= %d " , dblCorMax / dblCorMaxProduct , intJatMax ) ;
return intJatMax ;
}
Debugprintf ( " EnvelopeCorrelator failed %d " , dblCorMax / dblCorMaxProduct ) ;
return - 1 ;
}
// Function to acquire the Frame Sync for all Frames
BOOL AcquireFrameSyncRSB ( )
{
// Two improvements could be incorporated into this function:
// 1) Provide symbol tracking until the frame sync is found (small corrections should be less than 1 sample per 4 symbols ~2000 ppm)
// 2) Ability to more accurately locate the symbol center (could be handled by symbol tracking 1) above.
// This is for acquiring FSKFrameSync After Mixing Tones Mirrored around 1500 Hz. e.g. Reversed Sideband
// Frequency offset should be near 0 (normally within +/- 1 Hz)
// Locate the sync Symbol which has no phase change from the prior symbol (BPSK leader @ 1500 Hz)
int intLocalPtr = intMFSReadPtr ;
int intAvailableSymbols = ( intFilteredMixedSamplesLength - intMFSReadPtr ) / 240 ;
float dblPhaseSym1 ; //' phase of the first symbol
float dblPhaseSym2 ; //' phase of the second symbol
float dblPhaseSym3 ; //' phase of the third symbol
float dblReal , dblImag ;
float dblPhaseDiff12 , dblPhaseDiff23 ;
int i ;
if ( intAvailableSymbols < 3 )
return FALSE ; // must have at least 360 samples to search
// Calculate the Phase for the First symbol
GoertzelRealImag ( intFilteredMixedSamples , intLocalPtr , 240 , 30 , & dblReal , & dblImag ) ; // Carrier at 1500 Hz nominal Positioning with no cyclic prefix
dblPhaseSym1 = atan2f ( dblImag , dblReal ) ;
intLocalPtr + = 240 ; // advance one symbol
GoertzelRealImag ( intFilteredMixedSamples , intLocalPtr , 240 , 30 , & dblReal , & dblImag ) ; // Carrier at 1500 Hz nominal Positioning with no cyclic prefix
dblPhaseSym2 = atan2f ( dblImag , dblReal ) ;
intLocalPtr + = 240 ; // advance one symbol
for ( i = 0 ; i < = intAvailableSymbols - 3 ; i + + )
{
// Compute the phase of the next symbol
GoertzelRealImag ( intFilteredMixedSamples , intLocalPtr , 240 , 30 , & dblReal , & dblImag ) ; // Carrier at 1500 Hz nominal Positioning with no cyclic prefix
dblPhaseSym3 = atan2f ( dblImag , dblReal ) ;
// Compute the phase differences between sym1-sym2, sym2-sym3
dblPhaseDiff12 = dblPhaseSym1 - dblPhaseSym2 ;
if ( dblPhaseDiff12 > M_PI ) // bound phase diff to +/- Pi
dblPhaseDiff12 - = dbl2Pi ;
else if ( dblPhaseDiff12 < - M_PI )
dblPhaseDiff12 + = dbl2Pi ;
dblPhaseDiff23 = dblPhaseSym2 - dblPhaseSym3 ;
if ( dblPhaseDiff23 > M_PI ) // bound phase diff to +/- Pi
dblPhaseDiff23 - = dbl2Pi ;
else if ( dblPhaseDiff23 < - M_PI )
dblPhaseDiff23 + = dbl2Pi ;
if ( fabsf ( dblPhaseDiff12 ) > 0.6667f * M_PI & & fabsf ( dblPhaseDiff23 ) < 0.3333f * M_PI ) // Tighten the margin to 60 degrees
{
// intPSKRefPhase = (short)dblPhaseSym3 * 1000;
intLeaderRcvdMs = ( int ) ceil ( ( intLocalPtr - 30 ) / 12 ) ; // 30 is to accomodate offset of inital pointer for filter length.
intMFSReadPtr = intLocalPtr + 240 ; // Position read pointer to start of the symbol following reference symbol
if ( AccumulateStats )
intFrameSyncs + = 1 ; // accumulate tuning stats
//strDecodeCapture &= "Sync; Phase1>2=" & Format(dblPhaseDiff12, "0.00") & " Phase2>3=" & Format(dblPhaseDiff23, "0.00") & ": "
return TRUE ; // pointer is pointing to first 4FSK data symbol. (first symbol of frame type)
}
else
{
dblPhaseSym1 = dblPhaseSym2 ;
dblPhaseSym2 = dblPhaseSym3 ;
intLocalPtr + = 240 ; // advance one symbol
}
}
intMFSReadPtr = intLocalPtr - 480 ; // back up 2 symbols for next attempt (Current Sym2 will become new Sym1)
return FALSE ;
}
// Function to acquire the Frame Sync for all Frames using exponential averaging
int AcquireFrameSyncRSBAvg ( )
{
// This new routine uses exponential averaging on the ptr reference leader phases to minimize noise contribution
// Needs optimization of filter values and decision thresholds with actual simulator at low S:N and multipath.
// This is for acquiring FSKFrameSync After Mixing Tones Mirrored around 1500 Hz. e.g. Reversed Sideband
// Frequency offset should be near 0 (normally within +/- 1 Hz)
// Locate the sync Symbol which has no phase change from the prior symbol (50 baud BPSK leader @ 1500 Hz)
int intLocalPtr = intMFSReadPtr ;
int intAvailableSymbols = ( intFilteredMixedSamplesLength - intMFSReadPtr ) / 240 ;
float dblPhaseSym1 ; //' phase of the first symbol
float dblPhaseSym2 ; //' phase of the second symbol
float dblPhaseSym3 ; //' phase of the third symbol
float dblReal , dblImag ;
float dblPhaseDiff12 , dblPhaseDiff23 ;
int i ;
if ( intAvailableSymbols < 3 )
return FALSE ; // must have at least 360 samples to search
// Calculate the Phase for the First symbol
GoertzelRealImagHann120 ( intFilteredMixedSamples , intLocalPtr , 240 , 30 , & dblReal , & dblImag ) ; // Carrier at 1500 Hz nominal Positioning with no cyclic prefix
dblPhaseSym1 = atan2f ( dblImag , dblReal ) ;
intLocalPtr + = 240 ; // advance one symbol
GoertzelRealImagHann120 ( intFilteredMixedSamples , intLocalPtr , 240 , 30 , & dblReal , & dblImag ) ; // Carrier at 1500 Hz nominal Positioning with no cyclic prefix
dblPhaseSym2 = atan2f ( dblImag , dblReal ) ;
intLocalPtr + = 240 ; // advance one symbol
for ( i = 0 ; i < = intAvailableSymbols - 3 ; i + + )
{
// Compute the phase of the next symbol
GoertzelRealImagHann120 ( intFilteredMixedSamples , intLocalPtr , 240 , 30 , & dblReal , & dblImag ) ; // Carrier at 1500 Hz nominal Positioning with no cyclic prefix
dblPhaseSym3 = atan2f ( dblImag , dblReal ) ;
// Compute the phase differences between sym1-sym2, sym2-sym3
dblPhaseDiff12 = dblPhaseSym1 - dblPhaseSym2 ;
if ( dblPhaseDiff12 > M_PI ) // bound phase diff to +/- Pi
dblPhaseDiff12 - = dbl2Pi ;
else if ( dblPhaseDiff12 < - M_PI )
dblPhaseDiff12 + = dbl2Pi ;
if ( dblPhaseDiff1_2Avg > 9 )
dblPhaseDiff1_2Avg = fabsf ( dblPhaseDiff12 ) ; // initialize the difference average after a prior detect
else
dblPhaseDiff1_2Avg = 0.75 * dblPhaseDiff1_2Avg + 0.25 * fabsf ( dblPhaseDiff12 ) ; // exponential average
dblPhaseDiff23 = dblPhaseSym2 - dblPhaseSym3 ;
if ( dblPhaseDiff23 > M_PI ) // bound phase diff to +/- Pi
dblPhaseDiff23 - = dbl2Pi ;
else if ( dblPhaseDiff23 < - M_PI )
dblPhaseDiff23 + = dbl2Pi ;
if ( fabsf ( dblPhaseDiff1_2Avg ) > ( 0.83333 * M_PI ) & & fabsf ( dblPhaseDiff23 ) < ( 0.25f * M_PI ) ) // Margin ~30 deg and 45 degrees
{
intLeaderRcvdMs = ( int ) ceil ( ( intLocalPtr - 30 ) / 12 ) ; // 30 is to accomodate offset of inital pointer for filter length.
intMFSReadPtr = intLocalPtr + 240 ; // Position read pointer to start of the symbol following reference symbol
if ( AccumulateStats )
intFrameSyncs + = 1 ; // accumulate tuning stats
//strDecodeCapture &= "Sync; Phase1>2=" & Format(dblPhaseDiff12, "0.00") & " Phase2>3=" & Format(dblPhaseDiff23, "0.00") & ": "
// dttLastLeaderSync = Now;
dblPwrSNPower_dBPrior = - 1000 ; // Reset the prior Leader power to small value to insure minimum of two symbol passes on next leader detect.
return TRUE ; // pointer is pointing to first 4FSK data symbol. (first symbol of frame type)
}
// The following looks for phase errors (which should nomimally be Pi or 180 deg) and counts errors
// abandoning search on the second error, Then advancing the main intMFSReadPtr one symbol (240 samples) and returning to SearchingForLeader state.
if ( fabsf ( dblPhaseDiff1_2Avg ) < ( 0.6667 * M_PI ) | | fabsf ( dblPhaseDiff23 ) < ( 0.6667 * M_PI ) ) // Margin 60 deg
{
intPhaseError + = 1 ;
dblPhaseSym1 = dblPhaseSym2 ;
dblPhaseSym2 = dblPhaseSym3 ;
intLocalPtr + = 240 ; // advance one symbol
// if (intPhaseError > 1) // This bailout mechanism for sync failure is superior and doesn't make any assumptions about leader length
// {
// intMFSReadPtr += 240; // advance the MFSReadPointer one symbol and try to search for leader again.
// State = SearchingForLeader;
// return False;
// }
}
else
{
// keep searching available samples
dblPhaseSym1 = dblPhaseSym2 ;
dblPhaseSym2 = dblPhaseSym3 ;
intLocalPtr + = 240 ; // advance one symbol
}
}
intMFSReadPtr = intLocalPtr - 480 ; // back up 2 symbols for next attempt (Current Sym2 will become new Sym1)
return FALSE ;
}
// Function to Demod FrameType4FSK
BOOL DemodFrameType4FSK ( int intPtr , short * intSamples , int * intToneMags )
{
float dblReal , dblImag ;
int i ;
if ( ( intFilteredMixedSamplesLength - intPtr ) < 1920 ) // 8 symbols
return FALSE ;
intToneMagsLength = 8 ;
for ( i = 0 ; i < 8 ; i + + )
{
GoertzelRealImagHann120 ( intSamples , intPtr , 240 , 1350 / 50.0f , & dblReal , & dblImag ) ;
intToneMags [ 4 * i ] = ( int ) powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
GoertzelRealImagHann120 ( intSamples , intPtr , 240 , 1450 / 50.0f , & dblReal , & dblImag ) ;
intToneMags [ 1 + 4 * i ] = ( int ) powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
GoertzelRealImagHann120 ( intSamples , intPtr , 240 , 1550 / 50.0f , & dblReal , & dblImag ) ;
intToneMags [ 2 + 4 * i ] = ( int ) powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
GoertzelRealImagHann120 ( intSamples , intPtr , 240 , 1650 / 50.0f , & dblReal , & dblImag ) ;
intToneMags [ 3 + 4 * i ] = ( int ) powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
intPtr + = 240 ;
}
return TRUE ;
}
// Function to compute the "distance" from a specific bytFrame Xored by bytID using 1 symbol parity
float ComputeDecodeDistance ( int intTonePtr , int * intToneMags , UCHAR bytFrameType , UCHAR bytID )
{
// intTonePtr is the offset into the Frame type symbols. 0 for first Frame byte 16 = (4 x 4) for second frame byte
float dblDistance = 0 ;
int int4ToneSum ;
int intToneIndex ;
UCHAR bytMask = 0x30 ;
int j , k ;
for ( j = 0 ; j < = 3 ; j + + ) // over 4 symbols
{
int4ToneSum = 0 ;
for ( k = 0 ; k < = 3 ; k + + )
{
int4ToneSum + = intToneMags [ intTonePtr + ( 4 * j ) + k ] ;
}
if ( int4ToneSum = = 0 )
int4ToneSum = 1 ; // protects against possible overflow
if ( j < 3 )
intToneIndex = ( ( bytFrameType ^ bytID ) & bytMask ) > > ( 4 - 2 * j ) ;
else
intToneIndex = ComputeTypeParity ( bytFrameType ^ bytID ) ;
dblDistance + = 1.0f - ( ( 1.0f * intToneMags [ intTonePtr + ( 4 * j ) + ( 3 - intToneIndex ) ] ) / ( 1.0f * int4ToneSum ) ) ;
bytMask = bytMask > > 2 ;
}
dblDistance = dblDistance / 4 ; // normalize back to 0 to 1 range
return dblDistance ;
}
// A function to check the parity symbol used in the frame type decoding
BOOL CheckTypeParity ( UCHAR bytFrameType )
{
// Returns True if Parity OK
UCHAR bytMask = 0x30 ; // Look at only 6 bits of data (values only 0 to 63)
UCHAR bytParitySum = 3 ;
UCHAR bytSym = 0 ;
int k ;
for ( k = 0 ; k < 3 ; k + + )
{
bytSym = ( bytMask & bytFrameType ) > > ( 2 * ( 2 - k ) ) ;
bytParitySum = bytParitySum ^ bytSym ;
bytMask = bytMask > > 2 ;
}
return bytParitySum = = ( ( bytFrameType & 0x0C0 ) > > 6 ) ;
}
// Function to check Parity of frame type bytes
UCHAR GetFrameTypeByte ( int intTonePtr , int * intToneMags )
{
// Demodulate the byte pointed to postion of tone PTR and return it
UCHAR bytData = 0 , bytParity , bytSym ;
int intIndex = intTonePtr ;
int j ;
for ( j = 0 ; j < 4 ; j + + )
{
// for each 4FSK symbol (2 bits) in a byte
if ( intToneMags [ intIndex ] > intToneMags [ intIndex + 1 ] & & intToneMags [ intIndex ] > intToneMags [ intIndex + 2 ] & & intToneMags [ intIndex ] > intToneMags [ intIndex + 3 ] )
bytSym = 3 ;
else if ( intToneMags [ intIndex + 1 ] > intToneMags [ intIndex ] & & intToneMags [ intIndex + 1 ] > intToneMags [ intIndex + 2 ] & & intToneMags [ intIndex + 1 ] > intToneMags [ intIndex + 3 ] )
bytSym = 2 ;
else if ( intToneMags [ intIndex + 2 ] > intToneMags [ intIndex ] & & intToneMags [ intIndex + 2 ] > intToneMags [ intIndex + 1 ] & & intToneMags [ intIndex + 2 ] > intToneMags [ intIndex + 3 ] )
bytSym = 1 ;
else
bytSym = 0 ;
if ( j < 3 )
bytData = ( bytData < < 2 ) + bytSym ;
else
bytParity = bytSym < < 6 ;
intIndex + = 4 ;
}
return bytData | bytParity ;
}
BOOL CheckFrameTypeParity ( int intTonePtr , int * intToneMags )
{
// Demodulate the byte pointed to postion of tone PTR and check Parity Return True if OK
UCHAR bytData = GetFrameTypeByte ( intTonePtr , intToneMags ) ;
return CheckTypeParity ( bytData ) ;
}
// Function to compute the frame type by selecting the minimal distance from all valid frame types.
int MinimalDistanceFrameType ( int * intToneMags , UCHAR bytSessionID )
{
float dblMinDistance1 = 5 ; // minimal distance for the first byte initialize to large value
float dblMinDistance2 = 5 ; // minimal distance for the second byte initialize to large value
float dblMinDistance3 = 5 ; // minimal distance for the second byte under exceptional cases initialize to large value
int intIatMinDistance1 , intIatMinDistance2 , intIatMinDistance3 ;
float dblDistance1 , dblDistance2 , dblDistance3 ;
int i ;
strDecodeCapture [ 0 ] = 0 ;
if ( ProtocolState = = ISS )
{
bytValidFrameTypes = bytValidFrameTypesISS ;
bytValidFrameTypesLength = bytValidFrameTypesLengthISS ;
}
else
{
bytValidFrameTypes = bytValidFrameTypesALL ;
bytValidFrameTypesLength = bytValidFrameTypesLengthALL ;
}
// Search through all the valid frame types finding the minimal distance
// This looks like a lot of computation but measured < 1 ms for 135 iterations....RM 11/1/2016
for ( i = 0 ; i < bytValidFrameTypesLength ; i + + )
{
dblDistance1 = ComputeDecodeDistance ( 0 , intToneMags , bytValidFrameTypes [ i ] , 0 ) ;
dblDistance2 = ComputeDecodeDistance ( 16 , intToneMags , bytValidFrameTypes [ i ] , bytSessionID ) ;
if ( blnPending )
dblDistance3 = ComputeDecodeDistance ( 16 , intToneMags , bytValidFrameTypes [ i ] , 0x3F ) ;
else
dblDistance3 = ComputeDecodeDistance ( 16 , intToneMags , bytValidFrameTypes [ i ] , bytLastARQSessionID ) ;
if ( dblDistance1 < dblMinDistance1 )
{
dblMinDistance1 = dblDistance1 ;
intIatMinDistance1 = bytValidFrameTypes [ i ] ;
}
if ( dblDistance2 < dblMinDistance2 )
{
dblMinDistance2 = dblDistance2 ;
intIatMinDistance2 = bytValidFrameTypes [ i ] ;
}
if ( dblDistance3 < dblMinDistance3 )
{
dblMinDistance3 = dblDistance3 ;
intIatMinDistance3 = bytValidFrameTypes [ i ] ;
}
}
Debugprintf ( " Frame Decode type %x %x %x Dist %.2f %.2f %.2f Sess %x pend %d conn %d lastsess %d " ,
intIatMinDistance1 , intIatMinDistance2 , intIatMinDistance3 ,
dblMinDistance1 , dblMinDistance2 , dblMinDistance3 ,
bytSessionID , blnPending , blnARQConnected , bytLastARQSessionID ) ;
if ( bytSessionID = = 0x3F ) // ' we are in a FEC QSO, monitoring an ARQ session or have not yet reached the ARQ Pending or Connected status
{
if ( intIatMinDistance1 = = intIatMinDistance2 & & ( ( dblMinDistance1 < 0.3 ) | | ( dblMinDistance2 < 0.3 ) ) )
{
sprintf ( strDecodeCapture , " %s MD Decode;2 ID=H%X, Type=H%X:%s, D1= %.2f, D2= %.2f " ,
strDecodeCapture , bytSessionID , intIatMinDistance1 , Name ( intIatMinDistance1 ) , dblMinDistance1 , dblMinDistance2 ) ;
Debugprintf ( " [Frame Type Decode OK ] %s " , strDecodeCapture ) ;
dblOffsetLastGoodDecode = dblOffsetHz ;
return intIatMinDistance1 ;
}
if ( ( dblMinDistance1 < 0.3 ) & & CheckFrameTypeParity ( 0 , intToneMags ) & & IsDataFrame ( intIatMinDistance1 ) ) // this would handle the case of monitoring an ARQ connection where the SessionID is not 0x3F
{
sprintf ( strDecodeCapture , " %s MD Decode;3 ID=H%X, Type=H%X:%s, D1= %.2f, D2= %.2f " ,
strDecodeCapture , bytSessionID , intIatMinDistance1 , Name ( intIatMinDistance1 ) , dblMinDistance1 , dblMinDistance2 ) ;
Debugprintf ( " [Frame Type Decode OK ] %s " , strDecodeCapture ) ;
return intIatMinDistance1 ;
}
if ( ( dblMinDistance2 < 0.3 ) & & CheckFrameTypeParity ( 16 , intToneMags ) & & IsDataFrame ( intIatMinDistance2 ) ) // this would handle the case of monitoring an FEC transmission that failed above when the session ID is = 03F
{
sprintf ( strDecodeCapture , " %s MD Decode;4 ID=H%X, Type=H%X:%s, D1= %.2f, D2= %.2f " ,
strDecodeCapture , bytSessionID , intIatMinDistance1 , Name ( intIatMinDistance2 ) , dblMinDistance1 , dblMinDistance2 ) ;
Debugprintf ( " [Frame Type Decode OK ] %s " , strDecodeCapture ) ;
return intIatMinDistance2 ;
}
return - 1 ; // indicates poor quality decode so don't use
}
sprintf ( strDecodeCapture , " %s MD Decode;12 Type1=H%X: Type2=H%X: , D1= %.2f, D2= %.2f " ,
strDecodeCapture , intIatMinDistance1 , intIatMinDistance2 , dblMinDistance1 , dblMinDistance2 ) ;
Debugprintf ( " [Frame Type Decode Fail] %s " , strDecodeCapture ) ;
return - 1 ; // indicates poor quality decode so don't use
}
// Function to acquire the 4FSK frame type
int Acquire4FSKFrameType ( )
{
// intMFSReadPtr is pointing to start of first symbol of Frame Type (total of 8 4FSK symbols in frame type (2 bytes) + 1 parity symbol per byte
// returns -1 if minimal distance decoding is below threshold (low likelyhood of being correct)
// returns -2 if insufficient samples
// Else returns frame type 0-255
int NewType = 0 ;
char Offset [ 32 ] ;
if ( ( intFilteredMixedSamplesLength - intMFSReadPtr ) < ( 240 * 8 ) )
return - 2 ; // Check for 8 available 4FSK Symbols
if ( ! DemodFrameType4FSK ( intMFSReadPtr , intFilteredMixedSamples , & intToneMags [ 0 ] [ 0 ] ) )
{
Update4FSKConstellation ( & intToneMags [ 0 ] [ 0 ] , & intLastRcvdFrameQuality ) ;
intMFSReadPtr + = ( 240 * 8 ) ;
return - 1 ;
}
intRmtLeaderMeasure = ( Now - dttStartRmtLeaderMeasure ) ;
dttLastGoodFrameTypeDecode = Now ;
// Now do check received Tone array for testing minimum distance decoder
if ( blnPending ) // If we have a pending connection (btween the IRS first decode of ConReq until it receives a ConAck from the iSS)
NewType = MinimalDistanceFrameType ( & intToneMags [ 0 ] [ 0 ] , bytPendingSessionID ) ; // The pending session ID will become the session ID once connected)
else if ( blnARQConnected ) // If we are connected then just use the stcConnection.bytSessionID
NewType = MinimalDistanceFrameType ( & intToneMags [ 0 ] [ 0 ] , bytSessionID ) ;
else // not connected and not pending so use &FF (FEC or ARQ unconnected session ID
NewType = MinimalDistanceFrameType ( & intToneMags [ 0 ] [ 0 ] , 0x3F ) ;
sprintf ( Offset , " Offset %5.1f " , dblOffsetHz ) ;
SendtoGUI ( ' O ' , Offset , strlen ( Offset ) ) ;
if ( NewType > = 0 & & IsShortControlFrame ( NewType ) ) // update the constellation if a short frame (no data to follow)
Update4FSKConstellation ( & intToneMags [ 0 ] [ 0 ] , & intLastRcvdFrameQuality ) ;
if ( AccumulateStats )
if ( NewType > = 0 )
intGoodFSKFrameTypes + + ;
else
intFailedFSKFrameTypes + + ;
intMFSReadPtr + = ( 240 * 8 ) ; // advance to read pointer to the next symbol (if there is one)
return NewType ;
}
// Demodulate Functions. These are called repeatedly as samples arrive
// and buld a frame in static array bytFrameData
// Function to demodulate one carrier for all low baud rate 4FSK frame types
// Is called repeatedly to decode multitone modes
int Corrections = 0 ;
BOOL Demod1Car4FSK ( )
{
int Start = 0 ;
// We can't wait for the full frame as we don't have enough ram, so
// we do one character at a time, until we run out or end of frame
// Only continue if we have more than intSampPerSym * 4 chars
while ( State = = AcquireFrame )
{
if ( intFilteredMixedSamplesLength < ( ( intSampPerSym * 4 ) + 20 ) ) // allow for correcrions
{
// Move any unprocessessed data down buffer
// (while checking process - will use cyclic buffer eventually
// Debugprintf("Corrections %d", Corrections);
// If corrections is non-zero, we have to adjust
// number left
intFilteredMixedSamplesLength - = Corrections ;
if ( intFilteredMixedSamplesLength < 0 )
Debugprintf ( " Corrupt intFilteredMixedSamplesLength " ) ;
Corrections = 0 ;
if ( intFilteredMixedSamplesLength > 0 )
memmove ( intFilteredMixedSamples ,
& intFilteredMixedSamples [ Start ] , intFilteredMixedSamplesLength * 2 ) ;
return FALSE ;
}
// If this is a multicarrier mode, we must call the
// decode char routing for each carrier
switch ( intNumCar )
{
case 1 :
intCenterFreq = 1500 ;
if ( CarrierOk [ 0 ] = = FALSE ) // Don't redo if already decoded
Demod1Car4FSKChar ( Start , bytFrameData [ 0 ] , 0 ) ;
break ;
case 2 :
intCenterFreq = 1750 ;
if ( CarrierOk [ 0 ] = = FALSE )
Demod1Car4FSKChar ( Start , bytFrameData [ 0 ] , 0 ) ;
intCenterFreq = 1250 ;
if ( CarrierOk [ 1 ] = = FALSE )
Demod1Car4FSKChar ( Start , bytFrameData [ 1 ] , 1 ) ;
break ;
/* case 4:
intCenterFreq = 2250 ;
if ( CarrierOk [ 0 ] = = FALSE )
Demod1Car4FSKChar ( Start , bytFrameData1 , 0 ) ;
intCenterFreq = 1750 ;
if ( CarrierOk [ 1 ] = = FALSE )
Demod1Car4FSKChar ( Start , bytFrameData2 , 1 ) ;
intCenterFreq = 1250 ;
if ( CarrierOk [ 2 ] = = FALSE )
Demod1Car4FSKChar ( Start , bytFrameData3 , 2 ) ;
intCenterFreq = 750 ;
if ( CarrierOk [ 3 ] = = FALSE )
Demod1Car4FSKChar ( Start , bytFrameData4 , 3 ) ;
break ;
*/
}
charIndex + + ; // Index into received chars
SymbolsLeft - - ; // number still to decode
Start + = intSampPerSym * 4 ; // 4 FSK bit pairs per byte
intFilteredMixedSamplesLength - = intSampPerSym * 4 ;
if ( intFilteredMixedSamplesLength < 0 )
Debugprintf ( " Corrupt intFilteredMixedSamplesLength " ) ;
if ( SymbolsLeft = = 0 )
{
//- prepare for next
// If variable length packet frame header we only have header - leave rx running
if ( intFrameType = = PktFrameHeader )
{
State = SearchingForLeader ;
// Save any unused samples
if ( intFilteredMixedSamplesLength > 0 & & Start > 0 )
memmove ( intFilteredMixedSamples ,
& intFilteredMixedSamples [ Start ] , intFilteredMixedSamplesLength * 2 ) ;
return TRUE ;
}
DecodeCompleteTime = Now ;
DiscardOldSamples ( ) ;
ClearAllMixedSamples ( ) ;
State = SearchingForLeader ;
}
}
return TRUE ;
}
// Function to demodulate one carrier for all low baud rate 4FSK frame types
void Demod1Car4FSKChar ( int Start , UCHAR * Decoded , int Carrier )
{
// Converts intSamples to an array of bytes demodulating the 4FSK symbols with center freq intCenterFreq
// intPtr should be pointing to the approximate start of the first data symbol
// Updates bytData() with demodulated bytes
// Updates bytMinSymQuality with the minimum (range is 25 to 100) symbol making up each byte.
float dblReal , dblImag ;
float dblSearchFreq ;
float dblMagSum = 0 ;
float dblMag [ 4 ] ; // The magnitude for each of the 4FSK frequency bins
UCHAR bytSym ;
static UCHAR bytSymHistory [ 3 ] ;
int j ;
UCHAR bytData = 0 ;
int * intToneMagsptr = & intToneMags [ Carrier ] [ intToneMagsIndex [ Carrier ] ] ;
intToneMagsIndex [ Carrier ] + = 16 ;
// ReDim intToneMags(4 * intNumOfSymbols - 1)
// ReDim bytData(intNumOfSymbols \ 4 - 1)
if ( intBaud = = 100 )
dblSearchFreq = intCenterFreq + ( 1.5f * intBaud ) ; // the highest freq (equiv to lowest sent freq because of sideband reversal)
else
dblSearchFreq = intCenterFreq + ( 3.0f * intBaud ) ; // the highest freq (equiv to lowest sent freq because of sideband reversal)
// Do one symbol
for ( j = 0 ; j < 4 ; j + + ) // for each 4FSK symbol (2 bits) in a byte
{
dblMagSum = 0 ;
if ( intBaud = = 100 )
{
GoertzelRealImag ( intFilteredMixedSamples , Start , intSampPerSym , dblSearchFreq / intBaud , & dblReal , & dblImag ) ;
dblMag [ 0 ] = powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
dblMagSum + = dblMag [ 0 ] ;
GoertzelRealImag ( intFilteredMixedSamples , Start , intSampPerSym , ( dblSearchFreq - intBaud ) / intBaud , & dblReal , & dblImag ) ;
dblMag [ 1 ] = powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
dblMagSum + = dblMag [ 1 ] ;
GoertzelRealImag ( intFilteredMixedSamples , Start , intSampPerSym , ( dblSearchFreq - 2 * intBaud ) / intBaud , & dblReal , & dblImag ) ;
dblMag [ 2 ] = powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
dblMagSum + = dblMag [ 2 ] ;
GoertzelRealImag ( intFilteredMixedSamples , Start , intSampPerSym , ( dblSearchFreq - 3 * intBaud ) / intBaud , & dblReal , & dblImag ) ;
dblMag [ 3 ] = powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
dblMagSum + = dblMag [ 3 ] ;
}
else
{
dblMagSum = 0 ;
GoertzelRealImagHann120 ( intFilteredMixedSamples , Start , intSampPerSym , dblSearchFreq / intBaud , & dblReal , & dblImag ) ;
dblMag [ 0 ] = powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
dblMagSum + = dblMag [ 0 ] ;
GoertzelRealImagHann120 ( intFilteredMixedSamples , Start , intSampPerSym , ( dblSearchFreq - 2 * intBaud ) / intBaud , & dblReal , & dblImag ) ;
dblMag [ 1 ] = powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
dblMagSum + = dblMag [ 1 ] ;
GoertzelRealImagHann120 ( intFilteredMixedSamples , Start , intSampPerSym , ( dblSearchFreq - 4 * intBaud ) / intBaud , & dblReal , & dblImag ) ;
dblMag [ 2 ] = powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
dblMagSum + = dblMag [ 2 ] ;
GoertzelRealImagHann120 ( intFilteredMixedSamples , Start , intSampPerSym , ( dblSearchFreq - 6 * intBaud ) / intBaud , & dblReal , & dblImag ) ;
dblMag [ 3 ] = powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
dblMagSum + = dblMag [ 3 ] ;
}
if ( dblMag [ 0 ] > dblMag [ 1 ] & & dblMag [ 0 ] > dblMag [ 2 ] & & dblMag [ 0 ] > dblMag [ 3 ] )
bytSym = 0 ;
else if ( dblMag [ 1 ] > dblMag [ 0 ] & & dblMag [ 1 ] > dblMag [ 2 ] & & dblMag [ 1 ] > dblMag [ 3 ] )
bytSym = 1 ;
else if ( dblMag [ 2 ] > dblMag [ 0 ] & & dblMag [ 2 ] > dblMag [ 1 ] & & dblMag [ 2 ] > dblMag [ 3 ] )
bytSym = 2 ;
else
bytSym = 3 ;
bytData = ( bytData < < 2 ) + bytSym ;
// !!!!!!! this needs attention !!!!!!!!
* intToneMagsptr + + = dblMag [ 0 ] ;
* intToneMagsptr + + = dblMag [ 1 ] ;
* intToneMagsptr + + = dblMag [ 2 ] ;
* intToneMagsptr + + = dblMag [ 3 ] ;
bytSymHistory [ 0 ] = bytSymHistory [ 1 ] ;
bytSymHistory [ 1 ] = bytSymHistory [ 2 ] ;
bytSymHistory [ 2 ] = bytSym ;
// if ((bytSymHistory[0] != bytSymHistory[1]) && (bytSymHistory[1] != bytSymHistory[2]))
{
// only track when adjacent symbols are different (statistically about 56% of the time)
// this should allow tracking over 2000 ppm sampling rate error
// if (Start > intSampPerSym + 2)
// Track1Car4FSK(intFilteredMixedSamples, &Start, intSampPerSym, dblSearchFreq, intBaud, bytSymHistory);
}
Start + = intSampPerSym ; // advance the pointer one symbol
}
if ( AccumulateStats )
intFSKSymbolCnt + = 4 ;
Decoded [ charIndex ] = bytData ;
return ;
}
extern int intBW ;
// Function to Demodulate Frame based on frame type
// Will be called repeatedly as new samples arrive
void DemodulateFrame ( int intFrameType )
{
// Dim stcStatus As Status = Nothing
int intConstellationQuality = 0 ;
// ReDim bytData(-1)
strRcvFrameTag [ 0 ] = 0 ;
switch ( intFrameType )
{
case ConReq200 :
case ConReq500 :
case ConReq2500 :
case OConReq500 :
case OConReq2500 :
case PING :
case IDFRAME :
case PINGACK :
case CQ_de :
case PktFrameHeader : // Experimental Variable Length Frame
case OFDMACK :
Demod1Car4FSK ( ) ;
return ;
case PktFrameData : // Experimantal Variable Length Frame
if ( strcmp ( strMod , " 4FSK " ) = = 0 )
Demod1Car4FSK ( ) ;
else if ( strcmp ( strMod , " 16QAM " ) = = 0 )
DemodQAM ( ) ;
else
DemodPSK ( ) ;
return ;
}
switch ( intFrameType & 0xFE ) // Others are even/odd data frames
{
case D4FSK_500_50_E :
case D4FSK_1000_50_E :
Demod1Car4FSK ( ) ;
break ;
case D4PSK_200_50_E :
case D4PSK_200_100_E :
case D4PSK_500_50_E :
case D4PSK_500_100_E :
case D4PSKR_2500_50_E :
case D4PSK_2500_50_E :
case D4PSK_2500_100_E :
DemodPSK ( ) ;
break ;
case D16QAM_200_100_E :
case D16QAMR_500_100_E :
case D16QAM_500_100_E :
case D16QAMR_2500_100_E :
case D16QAM_2500_100_E :
DemodQAM ( ) ;
break ;
case DOFDM_200_55_E :
case DOFDM_500_55_E :
case DOFDM_2500_55_E :
DemodOFDM ( ) ;
break ;
default :
Debugprintf ( " Unsupported frame type %x " , intFrameType ) ;
DiscardOldSamples ( ) ;
ClearAllMixedSamples ( ) ;
State = SearchingForLeader ;
intFilteredMixedSamplesLength = 0 ; // Testing
}
}
int intSNdB = 0 , intQuality = 0 ;
2023-09-12 21:38:15 +01:00
BOOL DecodeFrame ( int chan , int xxx , UCHAR * bytData )
2023-09-04 19:06:44 +01:00
{
BOOL blnDecodeOK = FALSE ;
char strCallerCallsign [ 10 ] = " " ;
char strTargetCallsign [ 10 ] = " " ;
char strIDCallSign [ 11 ] = " " ;
char strGridSQ [ 20 ] = " " ;
char Reply [ 80 ] ;
strRcvFrameTag [ 0 ] = 0 ;
//DataACK/NAK and short control frames
if ( IsShortControlFrame ( intFrameType ) ) // Short Control Frames
{
blnDecodeOK = TRUE ;
DrawRXFrame ( 1 , Name ( intFrameType ) ) ;
goto returnframe ;
}
totalRSErrors = 0 ;
if ( IsDataFrame ( intFrameType ) )
PrintCarrierFlags ( ) ;
switch ( intFrameType )
{
case PktFrameHeader :
{
// Variable Length Packet Frame Header
// 6 bits Type 10 Bits Len
int Len ;
int pktNumCar ;
int pktDataLen ;
int pktRSLen ;
frameLen = CorrectRawDataWithRS ( & bytFrameData [ 0 ] [ 0 ] , bytData , intDataLen , intRSLen , intFrameType , 0 ) ;
if ( CarrierOk [ 0 ] )
{
pktRXMode = bytFrameData [ 0 ] [ 1 ] > > 2 ;
pktNumCar = pktCarriers [ pktRXMode ] ;
Len = ( ( bytFrameData [ 0 ] [ 1 ] & 0x3 ) < < 8 ) | bytFrameData [ 0 ] [ 2 ] ;
}
// Now only using one carrier
// else if (CarrierOk[1])
// {
// pktRXMode = bytFrameData2[1] >> 5;
// pktNumCar = ((bytFrameData2[1] & 0x1c) >> 2) + 1;
// Len = ((bytFrameData2[1] & 0x3) << 8) | bytFrameData2[2];
// }
else
{
// Cant decode
DiscardOldSamples ( ) ;
ClearAllMixedSamples ( ) ;
break ;
}
strcpy ( strMod , & pktMod [ pktRXMode ] [ 0 ] ) ;
// Reset to receive rest of frame
pktDataLen = ( Len + ( pktNumCar - 1 ) ) / pktNumCar ; // Round up
// This must match the encode settings
pktRSLen = pktDataLen > > 2 ; // Try 25% for now
if ( pktRSLen & 1 )
pktRSLen + + ; // Odd RS bytes no use
if ( pktRSLen < 4 )
pktRSLen = 4 ; // At least 4
SymbolsLeft = pktDataLen + pktRSLen + 3 ; // Data has crc + length byte
State = AcquireFrame ;
intFrameType = PktFrameData ;
CarrierOk [ 1 ] = CarrierOk [ 0 ] = 0 ;
charIndex = 0 ;
frameLen = 0 ;
intPhasesLen = 0 ;
memset ( intToneMagsIndex , 0 , sizeof ( intToneMagsIndex ) ) ;
intDataLen = pktDataLen ;
intRSLen = pktRSLen ;
intNumCar = pktNumCar ;
PSKInitDone = 0 ;
Debugprintf ( " Pkt Frame Header Type %s Len %d " , strMod , Len ) ;
strlop ( strMod , ' / ' ) ;
blnDecodeOK = TRUE ;
return 0 ;
}
case PktFrameData :
{
if ( pktFSK [ pktRXMode ] )
{
// Need to Check RS
frameLen = CorrectRawDataWithRS ( & bytFrameData [ 0 ] [ 0 ] , bytData , intDataLen , intRSLen , intFrameType , 0 ) ;
if ( intNumCar > 1 )
frameLen + = CorrectRawDataWithRS ( & bytFrameData [ 1 ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , 1 ) ;
if ( intNumCar > 2 )
{
frameLen + = CorrectRawDataWithRS ( & bytFrameData [ 2 ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , 2 ) ;
frameLen + = CorrectRawDataWithRS ( & bytFrameData [ 3 ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , 3 ) ;
}
}
if ( memcmp ( CarrierOk , Good , intNumCar ) = = 0 )
{
blnDecodeOK = TRUE ;
// Packet Data - if KISS interface ias active
// Pass to Host as KISS frame, else pass to
// Session code
// Data in bytData len in frameLen
2023-09-12 21:38:15 +01:00
ProcessPktFrame ( chan , bytData , frameLen ) ;
2023-09-04 19:06:44 +01:00
// else
// L2Routine(bytData, frameLen, intLastRcvdFrameQuality, totalRSErrors, intNumCar, pktRXMode);
}
break ;
}
default :
Debugprintf ( " Unrecognised frame type " ) ;
}
if ( blnDecodeOK )
{
Debugprintf ( " [DecodeFrame] Frame: %s Decode PASS, Constellation Quality= %d " , Name ( intFrameType ) , intLastRcvdFrameQuality ) ;
# ifdef PLOTCONSTELLATION
if ( intFrameType > = 0x30 & & intFrameType < = 0x38 )
DrawDecode ( lastGoodID ) ; // ID or CONREQ
else
DrawDecode ( " PASS " ) ;
updateDisplay ( ) ;
# endif
}
else
{
Debugprintf ( " [DecodeFrame] Frame: %s Decode FAIL, Constellation Quality= %d " , Name ( intFrameType ) , intLastRcvdFrameQuality ) ;
# ifdef PLOTCONSTELLATION
DrawDecode ( " FAIL " ) ;
updateDisplay ( ) ;
# endif
}
returnframe :
if ( blnDecodeOK & & IsDataFrame ( intFrameType ) )
bytLastReceivedDataFrameType = intFrameType ;
// if (DebugLog)
// if (blnDecodeOK)
// Debugprintf("[DecodeFrame] Frame: %s Decode PASS, Constellation Quality= %d", Name(intFrameType), intLastRcvdFrameQuality);
// else
// Debugprintf("[DecodeFrame] Frame: %s Decode FAIL, Constellation Quality= %d", Name(intFrameType), intLastRcvdFrameQuality);
return blnDecodeOK ;
}
// Subroutine to update the 4FSK Constellation
void drawFastVLine ( int x0 , int y0 , int length , int color ) ;
void drawFastHLine ( int x0 , int y0 , int length , int color ) ;
void Update4FSKConstellation ( int * intToneMags , int * intQuality )
{
// Subroutine to update bmpConstellation plot for 4FSK modes...
int intToneSum = 0 ;
int intMagMax = 0 ;
float dblPi4 = 0.25 * M_PI ;
float dblDistanceSum = 0 ;
int intRad = 0 ;
int i , x , y ;
int yCenter = 0 ;
int xCenter = 0 ;
# ifdef PLOTCONSTELLATION
int clrPixel ;
int yCenter = ( ConstellationHeight ) / 2 ;
int xCenter = ( ConstellationWidth ) / 2 ;
clearDisplay ( ) ;
# endif
for ( i = 0 ; i < intToneMagsLength ; i + = 4 ) // for the number of symbols represented by intToneMags
{
intToneSum = intToneMags [ i ] + intToneMags [ i + 1 ] + intToneMags [ i + 2 ] + intToneMags [ i + 3 ] ;
if ( intToneMags [ i ] > intToneMags [ i + 1 ] & & intToneMags [ i ] > intToneMags [ i + 2 ] & & intToneMags [ i ] > intToneMags [ i + 3 ] )
{
if ( intToneSum > 0 )
intRad = max ( 5 , 42 - 80 * ( intToneMags [ i + 1 ] + intToneMags [ i + 2 ] + intToneMags [ i + 3 ] ) / intToneSum ) ;
dblDistanceSum + = ( 42 - intRad ) ;
intRad = ( intRad * PLOTRADIUS ) / 50 ; // rescale for OLED (50 instead of 42 as we rotate constellation 35 degrees
x = xCenter + intRad ;
y = yCenter + intRad ;
}
else if ( intToneMags [ i + 1 ] > intToneMags [ i ] & & intToneMags [ i + 1 ] > intToneMags [ i + 2 ] & & intToneMags [ i + 1 ] > intToneMags [ i + 3 ] )
{
if ( intToneSum > 0 )
intRad = max ( 5 , 42 - 80 * ( intToneMags [ i ] + intToneMags [ i + 2 ] + intToneMags [ i + 3 ] ) / intToneSum ) ;
dblDistanceSum + = ( 42 - intRad ) ;
intRad = ( intRad * PLOTRADIUS ) / 50 ; // rescale for OLED (50 instead of 42 as we rotate constellation 35 degrees
x = xCenter + intRad ;
y = yCenter - intRad ;
}
else if ( intToneMags [ i + 2 ] > intToneMags [ i ] & & intToneMags [ i + 2 ] > intToneMags [ i + 1 ] & & intToneMags [ i + 2 ] > intToneMags [ i + 3 ] )
{
if ( intToneSum > 0 )
intRad = max ( 5 , 42 - 80 * ( intToneMags [ i + 1 ] + intToneMags [ i ] + intToneMags [ i + 3 ] ) / intToneSum ) ;
dblDistanceSum + = ( 42 - intRad ) ;
intRad = ( intRad * PLOTRADIUS ) / 50 ; // rescale for OLED (50 instead of 42 as we rotate constellation 35 degrees
x = xCenter - intRad ;
y = yCenter - intRad ;
}
else if ( intToneSum > 0 )
{
intRad = max ( 5 , 42 - 80 * ( intToneMags [ i + 1 ] + intToneMags [ i + 2 ] + intToneMags [ i ] ) / intToneSum ) ;
dblDistanceSum + = ( 42 - intRad ) ;
intRad = ( intRad * PLOTRADIUS ) / 50 ; // rescale for OLED (50 instead of 42 as we rotate constellation 35 degrees
x = xCenter - intRad ;
y = yCenter + intRad ;
}
# ifdef PLOTCONSTELLATION
if ( intRad < 15 )
clrPixel = Tomato ;
else if ( intRad < 30 )
clrPixel = Gold ;
else
clrPixel = Lime ;
mySetPixel ( x , y , clrPixel ) ;
# endif
}
* intQuality = 100 - ( 2.7f * ( dblDistanceSum / ( intToneMagsLength / 4 ) ) ) ; // ' factor 2.7 emperically chosen for calibration (Qual range 25 to 100)
if ( * intQuality < 0 )
* intQuality = 0 ;
else if ( * intQuality > 100 )
* intQuality = 100 ;
if ( AccumulateStats )
{
int4FSKQualityCnts + = 1 ;
int4FSKQuality + = * intQuality ;
}
# ifdef PLOTCONSTELLATION
DrawAxes ( * intQuality , shortName ( intFrameType ) , strMod ) ;
# endif
return ;
}
// Subroutine to update the 16FSK constallation
void Update16FSKConstellation ( int * intToneMags , int * intQuality )
{
// Subroutine to update bmpConstellation plot for 16FSK modes...
int intToneSum = 0 ;
float intMagMax = 0 ;
float dblDistanceSum = 0 ;
float dblPlotRotation = 0 ;
// Dim stcStatus As Status
int intRad ;
// Dim clrPixel As System.Drawing.Color
int intJatMaxMag ;
int i , j ;
# ifdef PLOTCONSTELLATION
float dblRad ;
float dblAng ;
int x , y , clrPixel ;
int yCenter = ( ConstellationHeight - 2 ) / 2 ;
int xCenter = ( ConstellationWidth - 2 ) / 2 ;
clearDisplay ( ) ;
# endif
for ( i = 0 ; i < intToneMagsLength ; i + = 16 ) // for the number of symbols represented by intToneMags
{
intToneSum = 0 ;
intMagMax = 0 ;
for ( j = 0 ; j < 16 ; j + + )
{
if ( intToneMags [ i + j ] > intMagMax )
{
intMagMax = intToneMags [ i + j ] ;
intJatMaxMag = j ;
}
intToneSum + = intToneMags [ i + j ] ;
}
intRad = max ( 5 , 42 - 40 * ( intToneSum - intMagMax ) / intToneSum ) ;
dblDistanceSum + = ( 43 - intRad ) ;
# ifdef PLOTCONSTELLATION
if ( intRad < 15 )
clrPixel = Tomato ;
else if ( intRad < 30 )
clrPixel = Gold ;
else
clrPixel = Lime ;
// plot the symbols rotated to avoid the axis
intRad = ( intRad * PLOTRADIUS ) / 42 ; // rescale for OLED
dblAng = M_PI / 16.0f + ( intJatMaxMag * M_PI / 8 ) ;
x = xCenter + intRad * cosf ( dblAng ) ;
y = yCenter + intRad * sinf ( dblAng ) ;
mySetPixel ( x , y , clrPixel ) ;
# endif
}
* intQuality = max ( 0 , ( 100 - 2.2 * ( dblDistanceSum / ( intToneMagsLength / 16 ) ) ) ) ; // factor 2.2 emperically chosen for calibration (Qual range 25 to 100)
// *intQuality = max(0, (100 - 1.0 * (dblDistanceSum / (intToneMagsLength / 16)))); // factor 2.2 emperically chosen for calibration (Qual range 25 to 100)
if ( AccumulateStats )
{
int16FSKQualityCnts + + ;
int16FSKQuality + = * intQuality ;
}
# ifdef PLOTCONSTELLATION
DrawAxes ( * intQuality , shortName ( intFrameType ) , strMod ) ;
# endif
}
// Subroutine to udpate the 8FSK Constellation
void Update8FSKConstellation ( int * intToneMags , int * intQuality )
{
// Subroutine to update bmpConstellation plot for 8FSK modes...
int intToneSum = 0 ;
int intMagMax = 0 ;
float dblPi4 = 0.25 * M_PI ;
float dblDistanceSum = 0 ;
int intRad = 0 ;
int i , j , intJatMaxMag ;
# ifdef PLOTCONSTELLATION
float dblAng ;
int yCenter = ( ConstellationHeight - 2 ) / 2 ;
int xCenter = ( ConstellationWidth - 2 ) / 2 ;
unsigned short clrPixel = WHITE ;
unsigned short x , y ;
clearDisplay ( ) ;
# endif
for ( i = 0 ; i < intToneMagsLength ; i + = 8 ) // for the number of symbols represented by intToneMags
{
intToneSum = 0 ;
intMagMax = 0 ;
for ( j = 0 ; j < 8 ; j + + )
{
if ( intToneMags [ i + j ] > intMagMax )
{
intMagMax = intToneMags [ i + j ] ;
intJatMaxMag = j ;
}
intToneSum + = intToneMags [ i + j ] ;
}
intRad = max ( 5 , 42 - 40 * ( intToneSum - intMagMax ) / intToneSum ) ;
dblDistanceSum + = ( 43 - intRad ) ;
# ifdef PLOTCONSTELLATION
if ( intRad < 15 )
clrPixel = Tomato ;
else if ( intRad < 30 )
clrPixel = Gold ;
else
clrPixel = Lime ;
// plot the symbols rotated to avoid the axis
intRad = ( intRad * PLOTRADIUS ) / 42 ; // rescale for OLED
dblAng = M_PI / 9.0f + ( intJatMaxMag * M_PI / 4 ) ;
x = xCenter + intRad * cosf ( dblAng ) ;
y = yCenter + intRad * sinf ( dblAng ) ;
mySetPixel ( x , y , clrPixel ) ;
# endif
}
* intQuality = max ( 0 , ( 100 - 2.0 * ( dblDistanceSum / ( intToneMagsLength / 8 ) ) ) ) ; // factor 2.0 emperically chosen for calibration (Qual range 25 to 100)
if ( AccumulateStats )
{
int8FSKQualityCnts + + ;
int8FSKQuality + = * intQuality ;
}
# ifdef PLOTCONSTELLATION
DrawAxes ( * intQuality , shortName ( intFrameType ) , strMod ) ;
# endif
return ;
}
// Subroutine to Update the PhaseConstellation
int UpdatePhaseConstellation ( short * intPhases , short * intMag , int intPSKPhase , BOOL blnQAM , BOOL OFDM )
{
// Subroutine to update bmpConstellation plot for PSK modes...
// Skip plotting and calculations of intPSKPhase(0) as this is a reference phase (9/30/2014)
float dblPhaseError ;
float dblPhaseErrorSum = 0 ;
int intPSKIndex ;
float intP = 0 ;
float dblRad = 0 ;
float dblAvgRad = 0 ;
float intMagMax = 0 ;
float dblPi4 = 0.25 * M_PI ;
float dbPhaseStep ;
float dblRadError = 0 ;
float dblPlotRotation = 0 ;
int intRadInner = 0 , intRadOuter = 0 ;
float dblAvgRadOuter = 0 , dblAvgRadInner = 0 , dblRadErrorInner = 0 , dblRadErrorOuter = 0 ;
int i , j , k , intQuality ;
# ifdef PLOTCONSTELLATION
int intX , intY ;
int yCenter = ( ConstellationHeight - 2 ) / 2 ;
int xCenter = ( ConstellationWidth - 2 ) / 2 ;
unsigned short clrPixel = WHITE ;
clearDisplay ( ) ;
# endif
if ( intPSKPhase = = 4 )
intPSKIndex = 0 ;
else
intPSKIndex = 1 ;
if ( blnQAM )
{
intPSKPhase = 8 ;
intPSKIndex = 1 ;
dbPhaseStep = 2 * M_PI / intPSKPhase ;
for ( j = 1 ; j < intPhasesLen ; j + + ) // skip the magnitude of the reference in calculation
{
intMagMax = max ( intMagMax , intMag [ j ] ) ; // find the max magnitude to auto scale
}
for ( k = 1 ; k < intPhasesLen ; k + + )
{
if ( intMag [ k ] < 0.75f * intMagMax )
{
dblAvgRadInner + = intMag [ k ] ;
intRadInner + + ;
}
else
{
dblAvgRadOuter + = intMag [ k ] ;
intRadOuter + + ;
}
}
dblAvgRadInner = dblAvgRadInner / intRadInner ;
dblAvgRadOuter = dblAvgRadOuter / intRadOuter ;
}
else
{
dbPhaseStep = 2 * M_PI / intPSKPhase ;
for ( j = 1 ; j < intPhasesLen ; j + + ) // skip the magnitude of the reference in calculation
{
intMagMax = max ( intMagMax , intMag [ j ] ) ; // find the max magnitude to auto scale
dblAvgRad + = intMag [ j ] ;
}
}
dblAvgRad = dblAvgRad / ( intPhasesLen - 1 ) ; // the average radius
for ( i = 1 ; i < intPhasesLen ; i + + ) // Don't plot the first phase (reference)
{
intP = round ( ( 0.001f * intPhases [ i ] ) / dbPhaseStep ) ;
// compute the Phase and Radius errors
if ( intMag [ i ] > ( dblAvgRadInner + dblAvgRadOuter ) / 2 )
dblRadErrorOuter + = fabsf ( dblAvgRadOuter - intMag [ i ] ) ;
else
dblRadErrorInner + = fabsf ( dblAvgRadInner - intMag [ i ] ) ;
dblPhaseError = fabsf ( ( ( 0.001 * intPhases [ i ] ) - intP * dbPhaseStep ) ) ; // always positive and < .5 * dblPhaseStep
dblPhaseErrorSum + = dblPhaseError ;
# ifdef PLOTCONSTELLATION
dblRad = PLOTRADIUS * intMag [ i ] / intMagMax ; // scale the radius dblRad based on intMagMax
intX = xCenter + dblRad * cosf ( dblPlotRotation + intPhases [ i ] / 1000.0f ) ;
intY = yCenter + dblRad * sinf ( dblPlotRotation + intPhases [ i ] / 1000.0f ) ;
if ( intX > 0 & & intY > 0 )
if ( intX ! = xCenter & & intY ! = yCenter )
mySetPixel ( intX , intY , Yellow ) ; // don't plot on top of axis
# endif
}
if ( blnQAM )
{
// intQuality = max(0, ((100 - 200 * (dblPhaseErrorSum / (intPhasesLen)) / dbPhaseStep))); // ignore radius error for (PSK) but include for QAM
intQuality = max ( 0 , ( 1 - ( dblRadErrorInner / ( intRadInner * dblAvgRadInner ) + dblRadErrorOuter / ( intRadOuter * dblAvgRadOuter ) ) ) * ( 100 - 200 * ( dblPhaseErrorSum / intPhasesLen ) / dbPhaseStep ) ) ;
// intQuality = max(0, ((100 - 200 * (dblPhaseErrorSum / (intPhasesLen)) / dbPhaseStep))); // ignore radius error for (PSK) but include for QAM
if ( AccumulateStats )
{
if ( OFDM )
{
intOFDMQualityCnts [ RXOFDMMode ] + + ;
intOFDMQuality [ RXOFDMMode ] + = intQuality ;
intOFDMSymbolsDecoded + = intPhasesLen ;
}
else
{
intQAMQualityCnts + = 1 ;
intQAMQuality + = intQuality ;
intQAMSymbolsDecoded + = intPhasesLen ;
}
}
}
else
{
intQuality = max ( 0 , ( ( 100 - 200 * ( dblPhaseErrorSum / ( intPhasesLen ) ) / dbPhaseStep ) ) ) ; // ignore radius error for (PSK) but include for QAM
if ( AccumulateStats )
{
if ( OFDM )
{
intOFDMQualityCnts [ RXOFDMMode ] + + ;
intOFDMQuality [ RXOFDMMode ] + = intQuality ;
intOFDMSymbolsDecoded + = intPhasesLen ;
}
else
{
intPSKQualityCnts [ intPSKIndex ] + + ;
intPSKQuality [ intPSKIndex ] + = intQuality ;
intPSKSymbolsDecoded + = intPhasesLen ;
}
}
}
# ifdef PLOTCONSTELLATION
DrawAxes ( intQuality , shortName ( intFrameType ) , strMod ) ;
# endif
return intQuality ;
}
// Subroutine to track 1 carrier 4FSK. Used for both single and multiple simultaneous carrier 4FSK modes.
VOID Track1Car4FSK ( short * intSamples , int * intPtr , int intSampPerSymbol , float dblSearchFreq , int intBaud , UCHAR * bytSymHistory )
{
// look at magnitude of the tone for bytHistory(1) 2 sample2 earlier and 2 samples later. and pick the maximum adjusting intPtr + or - 1
// this seems to work fine on test Mar 16, 2015. This should handle sample rate offsets (sender to receiver) up to about 2000 ppm
float dblReal , dblImag , dblMagEarly , dblMag , dblMagLate ;
float dblBinToSearch = ( dblSearchFreq - ( intBaud * bytSymHistory [ 1 ] ) ) / intBaud ; // select the 2nd last symbol for magnitude comparison
GoertzelRealImag ( intSamples , ( * intPtr - intSampPerSymbol - 2 ) , intSampPerSymbol , dblBinToSearch , & dblReal , & dblImag ) ;
dblMagEarly = powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
GoertzelRealImag ( intSamples , ( * intPtr - intSampPerSymbol ) , intSampPerSymbol , dblBinToSearch , & dblReal , & dblImag ) ;
dblMag = powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
GoertzelRealImag ( intSamples , ( * intPtr - intSampPerSymbol + 2 ) , intSampPerSymbol , dblBinToSearch , & dblReal , & dblImag ) ;
dblMagLate = powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ;
if ( dblMagEarly > dblMag & & dblMagEarly > dblMagLate )
{
* intPtr - - ;
Corrections - - ;
if ( AccumulateStats )
intAccumFSKTracking - - ;
}
else if ( dblMagLate > dblMag & & dblMagLate > dblMagEarly )
{
* intPtr + + ;
Corrections + + ;
if ( AccumulateStats )
intAccumFSKTracking + + ;
}
}
// Function to Decode one Carrier of PSK modulation
// Ideally want to be able to call on for each symbol, as I don't have the
// RAM to build whole frame
// Call for each set of 4 or 8 Phase Values
int pskStart = 0 ;
VOID Decode1CarPSK ( int Carrier , BOOL OFDM )
{
unsigned int int24Bits ;
UCHAR bytRawData ;
int k ;
int Len = intPhasesLen ;
UCHAR * Decoded ;
if ( OFDM )
Decoded = & bytFrameData [ 0 ] [ 0 ] ; // Always uses same buffer
else
{
if ( CarrierOk [ Carrier ] )
return ; // don't do it again
Decoded = & bytFrameData [ Carrier ] [ 0 ] ;
}
pskStart = 0 ;
charIndex = 0 ;
while ( Len > = 0 )
{
// Phase Samples are in intPhases
switch ( intPSKMode )
{
case 2 : // process 8 sequential phases per byte (1 bits per phase)
for ( k = 0 ; k < 8 ; k + + )
{
if ( k = = 0 )
bytRawData = 0 ;
else
bytRawData < < = 1 ;
if ( intPhases [ Carrier ] [ pskStart ] > = 1572 | | intPhases [ Carrier ] [ pskStart ] < = - 1572 )
bytRawData + = 1 ;
pskStart + + ;
}
Decoded [ charIndex + + ] = bytRawData ;
Len - = 8 ;
break ;
case 4 : // process 4 sequential phases per byte (2 bits per phase)
for ( k = 0 ; k < 4 ; k + + )
{
if ( k = = 0 )
bytRawData = 0 ;
else
bytRawData < < = 2 ;
if ( intPhases [ Carrier ] [ pskStart ] < 786 & & intPhases [ Carrier ] [ pskStart ] > - 786 )
{
} // Zero so no need to do anything
else if ( intPhases [ Carrier ] [ pskStart ] > = 786 & & intPhases [ Carrier ] [ pskStart ] < 2356 )
bytRawData + = 1 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = 2356 | | intPhases [ Carrier ] [ pskStart ] < = - 2356 )
bytRawData + = 2 ;
else
bytRawData + = 3 ;
pskStart + + ;
}
Decoded [ charIndex + + ] = bytRawData ;
Len - = 4 ;
break ;
case 8 : // Process 8 sequential phases (3 bits per phase) for 24 bits or 3 bytes
// Status verified on 1 Carrier 8PSK with no RS needed for High S/N
// Assume we check for 8 available phase samples before being called
int24Bits = 0 ;
for ( k = 0 ; k < 8 ; k + + )
{
int24Bits < < = 3 ;
if ( intPhases [ Carrier ] [ pskStart ] < 393 & & intPhases [ Carrier ] [ pskStart ] > - 393 )
{
} // Zero so no need to do anything
else if ( intPhases [ Carrier ] [ pskStart ] > = 393 & & intPhases [ Carrier ] [ pskStart ] < 1179 )
int24Bits + = 1 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = 1179 & & intPhases [ Carrier ] [ pskStart ] < 1965 )
int24Bits + = 2 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = 1965 & & intPhases [ Carrier ] [ pskStart ] < 2751 )
int24Bits + = 3 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = 2751 | | intPhases [ Carrier ] [ pskStart ] < - 2751 )
int24Bits + = 4 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = - 2751 & & intPhases [ Carrier ] [ pskStart ] < - 1965 )
int24Bits + = 5 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = - 1965 & & intPhases [ Carrier ] [ pskStart ] < = - 1179 )
int24Bits + = 6 ;
else
int24Bits + = 7 ;
pskStart + + ;
}
Decoded [ charIndex + + ] = int24Bits > > 16 ;
Decoded [ charIndex + + ] = int24Bits > > 8 ;
Decoded [ charIndex + + ] = int24Bits ;
Len - = 8 ;
break ;
case 16 : // Process 2 sequential phases (4 bits per phase) for 1 bytes
for ( k = 0 ; k < 2 ; k + + )
{
if ( k = = 0 )
bytRawData = 0 ;
else
bytRawData < < = 4 ;
if ( intPhases [ Carrier ] [ pskStart ] < 196 & & intPhases [ Carrier ] [ pskStart ] > - 196 )
{
} // Zero so no need to do anything
else if ( intPhases [ Carrier ] [ pskStart ] > = 196 & & intPhases [ Carrier ] [ pskStart ] < 589 )
bytRawData + = 1 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = 589 & & intPhases [ Carrier ] [ pskStart ] < 981 )
bytRawData + = 2 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = 981 & & intPhases [ Carrier ] [ pskStart ] < 1374 )
bytRawData + = 3 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = 1374 & & intPhases [ Carrier ] [ pskStart ] < 1766 )
bytRawData + = 4 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = 1766 & & intPhases [ Carrier ] [ pskStart ] < 2159 )
bytRawData + = 5 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = 2159 & & intPhases [ Carrier ] [ pskStart ] < 2551 )
bytRawData + = 6 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = 2551 & & intPhases [ Carrier ] [ pskStart ] < 2944 )
bytRawData + = 7 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = 2944 | | intPhases [ Carrier ] [ pskStart ] < - 2944 )
bytRawData + = 8 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = - 2944 & & intPhases [ Carrier ] [ pskStart ] < - 2551 )
bytRawData + = 9 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = - 2551 & & intPhases [ Carrier ] [ pskStart ] < - 2159 )
bytRawData + = 10 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = - 2159 & & intPhases [ Carrier ] [ pskStart ] < - 1766 )
bytRawData + = 11 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = - 1766 & & intPhases [ Carrier ] [ pskStart ] < - 1374 )
bytRawData + = 12 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = - 1374 & & intPhases [ Carrier ] [ pskStart ] < - 981 )
bytRawData + = 13 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = - 981 & & intPhases [ Carrier ] [ pskStart ] < - 589 )
bytRawData + = 14 ;
else
bytRawData + = 15 ;
pskStart + + ;
}
Decoded [ charIndex + + ] = bytRawData ;
Len - = 2 ;
break ;
default :
return ; //????
}
}
return ;
}
// Function to compute PSK symbol tracking (all PSK modes, used for single or multiple carrier modes)
int Track1CarPSK ( int floatCarFreq , int PSKMode , BOOL QAM , BOOL OFDM , float dblUnfilteredPhase , BOOL blnInit )
{
// This routine initializes and tracks the phase offset per symbol and adjust intPtr +/-1 when the offset creeps to a threshold value.
// adjusts (by Ref) intPtr 0, -1 or +1 based on a filtering of phase offset.
// this seems to work fine on test Mar 21, 2015. May need optimization after testing with higher sample rate errors.
// This should handle sample rate offsets (sender to receiver) up to about 2000 ppm
float dblAlpha = 0.3f ; // low pass filter constant may want to optimize value after testing with large sample rate error.
// (Affects how much averaging is done) lower values of dblAlpha will minimize adjustments but track more slugishly.
float dblPhaseOffset ;
static float dblTrackingPhase = 0 ;
static float dblModFactor ;
static float dblRadiansPerSample ; // range is .4188 @ car freq = 800 to 1.1195 @ car freq 2200
static float dblPhaseAtLastTrack ;
static int intCountAtLastTrack ;
static float dblFilteredPhaseOffset ;
if ( blnInit )
{
// dblFilterredPhase = dblUnfilteredPhase;
dblTrackingPhase = dblUnfilteredPhase ;
if ( PSKMode = = 16 )
dblModFactor = M_PI / 8 ;
else if ( PSKMode = = 8 )
dblModFactor = M_PI / 4 ;
else if ( PSKMode = = 4 )
dblModFactor = M_PI / 2 ;
else
dblModFactor = M_PI ; // 2PSK
dblRadiansPerSample = ( floatCarFreq * dbl2Pi ) / 12000.0f ;
dblPhaseOffset = dblUnfilteredPhase - dblModFactor * round ( dblUnfilteredPhase / dblModFactor ) ;
dblPhaseAtLastTrack = dblPhaseOffset ;
dblFilteredPhaseOffset = dblPhaseOffset ;
intCountAtLastTrack = 0 ;
return 0 ;
}
intCountAtLastTrack + = 1 ;
dblPhaseOffset = dblUnfilteredPhase - dblModFactor * round ( dblUnfilteredPhase / dblModFactor ) ;
dblFilteredPhaseOffset = ( 1 - dblAlpha ) * dblFilteredPhaseOffset + dblAlpha * dblPhaseOffset ;
if ( ( dblFilteredPhaseOffset - dblPhaseAtLastTrack ) > dblRadiansPerSample )
{
//Debug.WriteLine("Filtered>LastTrack: Cnt=" & intCountAtLastTrack.ToString & " Filtered = " & Format(dblFilteredPhaseOffset, "00.000") & " Offset = " & Format(dblPhaseOffset, "00.000") & " Unfiltered = " & Format(dblUnfilteredPhase, "00.000"))
dblFilteredPhaseOffset = dblPhaseOffset - dblRadiansPerSample ;
dblPhaseAtLastTrack = dblFilteredPhaseOffset ;
if ( AccumulateStats )
{
if ( OFDM )
{
intOFDMTrackAttempts + + ;
intAccumOFDMTracking - - ;
}
else
if ( QAM )
{
intQAMTrackAttempts + + ;
intAccumQAMTracking - - ;
}
else
{
intPSKTrackAttempts + + ;
intAccumPSKTracking - - ;
}
}
return - 1 ;
}
if ( ( dblPhaseAtLastTrack - dblFilteredPhaseOffset ) > dblRadiansPerSample )
{
//'Debug.WriteLine("Filtered<LastTrack: Cnt=" & intCountAtLastTrack.ToString & " Filtered = " & Format(dblFilteredPhaseOffset, "00.000") & " Offset = " & Format(dblPhaseOffset, "00.000") & " Unfiltered = " & Format(dblUnfilteredPhase, "00.000"))
dblFilteredPhaseOffset = dblPhaseOffset + dblRadiansPerSample ;
dblPhaseAtLastTrack = dblFilteredPhaseOffset ;
if ( AccumulateStats )
{
if ( OFDM )
{
intOFDMTrackAttempts + + ;
intAccumOFDMTracking + + ;
}
else
if ( QAM ) // 16QAM" Then
{
intQAMTrackAttempts + + ;
intAccumQAMTracking + + ;
}
else
{
intPSKTrackAttempts + + ;
intAccumPSKTracking + + ;
}
}
return 1 ;
}
// 'Debug.WriteLine("Filtered Phase = " & Format(dblFilteredPhaseOffset, "00.000") & " Offset = " & Format(dblPhaseOffset, "00.000") & " Unfiltered = " & Format(dblUnfilteredPhase, "00.000"))
return 0 ;
}
// Function to compute the differenc of two angles
int ComputeAng1_Ang2 ( int intAng1 , int intAng2 )
{
// do an angle subtraction intAng1 minus intAng2 (in milliradians)
// Results always between -3142 and 3142 (+/- Pi)
int intDiff ;
intDiff = intAng1 - intAng2 ;
if ( intDiff < - 3142 )
intDiff + = 6284 ;
else if ( intDiff > 3142 )
intDiff - = 6284 ;
return intDiff ;
}
// Subroutine to "rotate" the phases to try and set the average offset to 0.
void CorrectPhaseForTuningOffset ( short * intPhase , int intPhaseLength , int intPSKMode )
{
// A tunning error of -1 Hz will rotate the phase calculation Clockwise ~ 64 milliradians (~4 degrees)
// This corrects for:
// 1) Small tuning errors which result in a phase bias (rotation) of then entire constellation
// 2) Small Transmitter/receiver drift during the frame by averaging and adjusting to constellation to the average.
// It only processes phase values close to the nominal to avoid generating too large of a correction from outliers: +/- 30 deg for 4PSK, +/- 15 deg for 8PSK
// Is very affective in handling initial tuning error.
short intPhaseMargin = 2095 / intPSKMode ; // Compute the acceptable phase correction range (+/-30 degrees for 4 PSK)
short intPhaseInc = 6284 / intPSKMode ;
int intTest ;
int i ;
int intOffset , intAvgOffset , intAvgOffsetBeginning , intAvgOffsetEnd ;
int intAccOffsetCnt = 0 , intAccOffsetCntBeginning = 0 , intAccOffsetCntEnd = 0 ;
int intAccOffsetBeginning = 0 , intAccOffsetEnd = 0 , intAccOffset = 0 ;
// Note Rev 0.6.2.4 The following phase margin value increased from 2095 (120 deg) to 2793 (160 deg) yielded an improvement in decode at low S:N
intPhaseMargin = 2793 / intPSKMode ; // Compute the acceptable phase correction range (+/-30 degrees for 4 PSK)
intPhaseInc = 6284 / intPSKMode ;
// Compute the average offset (rotation) for all symbols within +/- intPhaseMargin of nominal
for ( i = 0 ; i < intPhaseLength ; i + + )
{
intTest = ( intPhase [ i ] / intPhaseInc ) ;
intOffset = intPhase [ i ] - intTest * intPhaseInc ;
if ( ( intOffset > = 0 & & intOffset < = intPhaseMargin ) | | ( intOffset < 0 & & intOffset > = - intPhaseMargin ) )
{
intAccOffsetCnt + = 1 ;
intAccOffset + = intOffset ;
if ( i < = intPhaseLength / 4 )
{
intAccOffsetCntBeginning + = 1 ;
intAccOffsetBeginning + = intOffset ;
}
else if ( i > = ( 3 * intPhaseLength ) / 4 )
{
intAccOffsetCntEnd + = 1 ;
intAccOffsetEnd + = intOffset ;
}
}
}
if ( intAccOffsetCnt > 0 )
intAvgOffset = ( intAccOffset / intAccOffsetCnt ) ;
if ( intAccOffsetCntBeginning > 0 )
intAvgOffsetBeginning = ( intAccOffsetBeginning / intAccOffsetCntBeginning ) ;
if ( intAccOffsetCntEnd > 0 )
intAvgOffsetEnd = ( intAccOffsetEnd / intAccOffsetCntEnd ) ;
//Debugprintf("[CorrectPhaseForOffset] Beginning: %d End: %d Total: %d",
//intAvgOffsetBeginning, intAvgOffsetEnd, intAvgOffset);
if ( ( intAccOffsetCntBeginning > intPhaseLength / 8 ) & & ( intAccOffsetCntEnd > intPhaseLength / 8 ) )
{
for ( i = 0 ; i < intPhaseLength ; i + + )
{
intPhase [ i ] = intPhase [ i ] - ( ( intAvgOffsetBeginning * ( intPhaseLength - i ) / intPhaseLength ) + ( intAvgOffsetEnd * i / intPhaseLength ) ) ;
if ( intPhase [ i ] > 3142 )
intPhase [ i ] - = 6284 ;
else if ( intPhase [ i ] < - 3142 )
intPhase [ i ] + = 6284 ;
}
Debugprintf ( " [CorrectPhaseForTuningOffset] AvgOffsetBeginning=%d AvgOffsetEnd=%d AccOffsetCnt=%d/%d " ,
intAvgOffsetBeginning , intAvgOffsetEnd , intAccOffsetCnt , intPhaseLength ) ;
}
else if ( intAccOffsetCnt > intPhaseLength / 2 )
{
for ( i = 0 ; i < intPhaseLength ; i + + )
{
intPhase [ i ] - = intAvgOffset ;
if ( intPhase [ i ] > 3142 )
intPhase [ i ] - = 6284 ;
else if ( intPhase [ i ] < - 3142 )
intPhase [ i ] + = 6284 ;
}
Debugprintf ( " [CorrectPhaseForTuningOffset] AvgOffset=%d AccOffsetCnt=%d/%d " ,
intAvgOffset , intAccOffsetCnt , intPhaseLength ) ;
}
}
// Function to Decode one Carrier of 16QAM modulation
// Call for each set of 4 or 8 Phase Values
short intCarMagThreshold [ MAXCAR ] = { 0 } ;
VOID Decode1CarQAM ( int Carrier )
{
unsigned int intData ;
int k ;
float dblAlpha = 0.1f ; // this determins how quickly the rolling average dblTrackingThreshold responds.
// dblAlpha value of .1 seems to work well...needs to be tested on fading channel (e.g. Multipath)
int Threshold = intCarMagThreshold [ Carrier ] ;
int Len = intPhasesLen ;
UCHAR * Decoded = bytFrameData [ Carrier ] ;
if ( CarrierOk [ Carrier ] )
return ; // don't do it again
pskStart = 0 ;
charIndex = 0 ;
// We calculated initial mag from reference symbol
// use filtered tracking of refernce phase amplitude
// (should be full amplitude value)
// On WGN this appears to improve decoding threshold about 1 dB 9/3/2016
while ( Len > = 0 )
{
// Phase Samples are in intPhases
intData = 0 ;
for ( k = 0 ; k < 2 ; k + + )
{
intData < < = 4 ;
if ( intPhases [ Carrier ] [ pskStart ] < 393 & & intPhases [ Carrier ] [ pskStart ] > - 393 )
{
} // Zero so no need to do anything
else if ( intPhases [ Carrier ] [ pskStart ] > = 393 & & intPhases [ Carrier ] [ pskStart ] < 1179 )
intData + = 1 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = 1179 & & intPhases [ Carrier ] [ pskStart ] < 1965 )
intData + = 2 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = 1965 & & intPhases [ Carrier ] [ pskStart ] < 2751 )
intData + = 3 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = 2751 | | intPhases [ Carrier ] [ pskStart ] < - 2751 )
intData + = 4 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = - 2751 & & intPhases [ Carrier ] [ pskStart ] < - 1965 )
intData + = 5 ;
else if ( intPhases [ Carrier ] [ pskStart ] > = - 1965 & & intPhases [ Carrier ] [ pskStart ] < = - 1179 )
intData + = 6 ;
else
intData + = 7 ;
if ( intMags [ Carrier ] [ pskStart ] < Threshold )
{
intData + = 8 ; // add 8 to "inner circle" symbols.
Threshold = ( Threshold * 900 + intMags [ Carrier ] [ pskStart ] * 150 ) / 1000 ;
}
else
{
Threshold = ( Threshold * 900 + intMags [ Carrier ] [ pskStart ] * 75 ) / 1000 ;
}
intCarMagThreshold [ Carrier ] = Threshold ;
pskStart + + ;
}
Decoded [ charIndex + + ] = intData ;
Len - = 2 ;
}
}
// Functions to demod all PSKData frames single or multiple carriers
VOID InitDemodPSK ( )
{
// Called at start of frame
int i ;
float dblPhase , dblReal , dblImag ;
intPSKMode = strMod [ 0 ] - ' 0 ' ;
PSKInitDone = TRUE ;
intPhasesLen = 0 ;
if ( intPSKMode = = 8 )
dblPhaseInc = 2 * M_PI * 1000 / 8 ;
else
dblPhaseInc = 2 * M_PI * 1000 / 4 ;
if ( intBaud = = 50 )
intSampPerSym = 240 ;
else
intSampPerSym = 120 ;
if ( intNumCar = = 1 )
floatCarFreq = 1500 ;
else
floatCarFreq = 1400 + ( intNumCar / 2 ) * 200 ; // start at the highest carrier freq which is actually the lowest transmitted carrier due to Reverse sideband mixing
for ( i = 0 ; i < intNumCar ; i + + )
{
if ( intBaud = = 50 )
{
intCP [ i ] = 0 ;
intNforGoertzel [ i ] = 240 ;
dblFreqBin [ i ] = floatCarFreq / 50 ;
}
else if ( intBaud = = 100 )
{
//Experimental use of Hanning Windowing
intNforGoertzel [ i ] = 120 ;
dblFreqBin [ i ] = floatCarFreq / 100 ;
intCP [ i ] = 0 ;
}
/* if (intBaud == 100 && floatCarFreq == 1500)
{
intCP [ i ] = 20 ; // These values selected for best decode percentage (92%) and best average 4PSK Quality (82) on MPP0dB channel
dblFreqBin [ i ] = floatCarFreq / 150 ;
intNforGoertzel [ i ] = 80 ;
}
else if ( intBaud = = 100 )
{
intCP [ i ] = 28 ; // This value selected for best decoding percentage (56%) and best Averag 4PSK Quality (77) on mpg +5 dB
intNforGoertzel [ i ] = 60 ;
dblFreqBin [ i ] = floatCarFreq / 200 ;
}
else if ( intBaud = = 167 )
{
intCP [ i ] = 6 ; // Need to optimize (little difference between 6 and 12 @ wgn5, 2 Car 500 Hz)
intNforGoertzel [ i ] = 60 ;
dblFreqBin [ i ] = floatCarFreq / 200 ;
}
*/
// Get initial Reference Phase
GoertzelRealImagHann120 ( intFilteredMixedSamples , 0 , intNforGoertzel [ i ] , dblFreqBin [ i ] , & dblReal , & dblImag ) ;
dblPhase = atan2f ( dblImag , dblReal ) ;
Track1CarPSK ( floatCarFreq , strMod [ 0 ] - ' 0 ' , FALSE , FALSE , dblPhase , TRUE ) ;
intPSKPhase_1 [ i ] = - 1000 * dblPhase ; // negative sign compensates for phase reverse after mixing
// Set initial mag from Reference Phase (which should be full power)
// Done here as well as in initQAM for pkt where we may switch mode midpacket
intCarMagThreshold [ i ] = sqrtf ( powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ) ;
intCarMagThreshold [ i ] * = 0.75 ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
}
}
int Demod1CarPSKChar ( int Start , int Carrier ) ;
void SavePSKSamples ( int i ) ;
short WeightedAngleAvg ( short intAng1 , short intAng2 ) ;
int CheckCarrierPairPSK ( int Base , int Dup , int frameLen )
{
int i , Len ;
Debugprintf ( " DemodPSK Carriers %d and %d " , Base , Dup ) ;
Decode1CarPSK ( Base , FALSE ) ;
Len = CorrectRawDataWithRS ( & bytFrameData [ Base ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , Base ) ;
if ( CarrierOk [ Base ] )
{
// No need to decode 2nd
CarrierOk [ Dup ] = 1 ; // So FrameOk test is passed
return Len + frameLen ;
}
Debugprintf ( " DemodPSK Carrier %d bad, trying %d " , Base , Dup ) ;
Decode1CarPSK ( Dup , FALSE ) ; // Decode Dup carrier
Len = CorrectRawDataWithRS ( & bytFrameData [ Dup ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , Base ) ; // Save as carrier 1
if ( CarrierOk [ Base ] )
{
CarrierOk [ Dup ] = 1 ; // So FrameOk test is passed
bytFrameData [ Base ] [ 0 ] = Len ;
memcpy ( & bytFrameData [ Base ] [ 1 ] , & bytData [ frameLen ] , Len ) ; // Any retry will use first copy without new decode
return Len + frameLen ;
}
// Try to average phases for the two carriers
Debugprintf ( " DemodPSK both bad, trying average " ) ;
for ( i = 0 ; i < intPhasesLen ; i + + )
{
intPhases [ Base ] [ i ] = WeightedAngleAvg ( intPhases [ Base ] [ i ] , intPhases [ Dup ] [ i ] ) ;
}
// Try decode again on averages
Decode1CarPSK ( Base , FALSE ) ;
Len = CorrectRawDataWithRS ( & bytFrameData [ Base ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , Base ) ;
if ( CarrierOk [ Base ] )
CarrierOk [ Dup ] = 1 ; // So FrameOk test is passed
return Len + frameLen ;
}
void DemodPSK ( )
{
int Used [ MAXCAR ] = { 0 } , Carrier ;
int Start = 0 , i ;
int MemARQRetries = 0 ;
// We can't wait for the full frame as we don't have enough RAM, so
// we do one DMA Buffer at a time, until we run out or end of frame
// Only continue if we have enough samples
intPSKMode = strMod [ 0 ] - ' 0 ' ;
while ( State = = AcquireFrame )
{
if ( intFilteredMixedSamplesLength < intPSKMode * intSampPerSym + 10 ) // allow for a few phase corrections
{
// Move any unprocessessed data down buffer
// (while checking process - will use cyclic buffer eventually
if ( intFilteredMixedSamplesLength > 0 & & Start > 0 )
memmove ( intFilteredMixedSamples ,
& intFilteredMixedSamples [ Start ] , intFilteredMixedSamplesLength * 2 ) ;
return ;
}
if ( PSKInitDone = = 0 ) // First time through
{
if ( intFilteredMixedSamplesLength < 2 * intPSKMode * intSampPerSym + 10 )
return ; // Wait for at least 2 chars worth
InitDemodPSK ( ) ;
intFilteredMixedSamplesLength - = intSampPerSym ;
if ( intFilteredMixedSamplesLength < 0 )
Debugprintf ( " Corrupt intFilteredMixedSamplesLength " ) ;
Start + = intSampPerSym ;
}
// If this is a multicarrier mode, we must call the
// decode char routing for each carrier
if ( intNumCar = = 1 )
floatCarFreq = 1500 ;
else
floatCarFreq = 1400 + ( intNumCar / 2 ) * 200 ; // start at the highest carrier freq which is actually the lowest transmitted carrier due to Reverse sideband mixing
Used [ 0 ] = Demod1CarPSKChar ( Start , 0 ) ;
if ( intNumCar > 1 )
{
intPhasesLen - = intPSKMode ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used [ 1 ] = Demod1CarPSKChar ( Start , 1 ) ;
}
if ( intNumCar > 2 )
{
intPhasesLen - = intPSKMode ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used [ 2 ] = Demod1CarPSKChar ( Start , 2 ) ;
intPhasesLen - = intPSKMode ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used [ 3 ] = Demod1CarPSKChar ( Start , 3 ) ;
}
if ( intNumCar > 4 )
{
intPhasesLen - = intPSKMode ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used [ 4 ] = Demod1CarPSKChar ( Start , 4 ) ;
intPhasesLen - = intPSKMode ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used [ 5 ] = Demod1CarPSKChar ( Start , 5 ) ;
intPhasesLen - = intPSKMode ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used [ 6 ] = Demod1CarPSKChar ( Start , 6 ) ;
intPhasesLen - = intPSKMode ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used [ 7 ] = Demod1CarPSKChar ( Start , 7 ) ;
intPhasesLen - = intPSKMode ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used [ 8 ] = Demod1CarPSKChar ( Start , 8 ) ;
intPhasesLen - = intPSKMode ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Used [ 9 ] = Demod1CarPSKChar ( Start , 9 ) ;
}
if ( intPSKMode = = 4 )
SymbolsLeft - - ; // number still to decode
else
SymbolsLeft - = 3 ;
// If/when we reenable phase correstion we can take average of Used values.
// ?? Should be also keep start value per carrier ??
Start + = Used [ 0 ] ;
intFilteredMixedSamplesLength - = Used [ 0 ] ;
if ( intFilteredMixedSamplesLength < 0 )
Debugprintf ( " Corrupt intFilteredMixedSamplesLength " ) ;
if ( SymbolsLeft > 0 )
continue ;
// Decode the phases
DecodeCompleteTime = Now ;
// CorrectPhaseForTuningOffset(&intPhases[0][0], intPhasesLen, strMod);
// if (intNumCar > 1)
// CorrectPhaseForTuningOffset(&intPhases[1][0], intPhasesLen, strMod);
if ( intNumCar > 2 )
{
// CorrectPhaseForTuningOffset(&intPhases[2][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[3][0], intPhasesLen, strMod);
}
if ( intNumCar > 4 )
{
// CorrectPhaseForTuningOffset(&intPhases[4][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[5][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[6][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[7][0], intPhasesLen, strMod);
}
// Rick uses the last carier for Quality
intLastRcvdFrameQuality = UpdatePhaseConstellation ( & intPhases [ intNumCar - 1 ] [ 0 ] , & intMags [ intNumCar - 1 ] [ 0 ] , strMod [ 0 ] - ' 0 ' , FALSE , FALSE ) ;
// prepare for next
State = SearchingForLeader ;
DiscardOldSamples ( ) ;
ClearAllMixedSamples ( ) ;
if ( strchr ( strMod , ' R ' ) )
{
// Robust Mode - data is repeated (1-2 or 1-6, 2-7, etc
if ( intNumCar = = 2 )
{
frameLen = CheckCarrierPairPSK ( 0 , 1 , 0 ) ;
return ;
}
//Only have 2 or 10 (500 or 2500 modes)
frameLen = CheckCarrierPairPSK ( 0 , 5 , 0 ) ;
frameLen = CheckCarrierPairPSK ( 1 , 6 , frameLen ) ;
frameLen = CheckCarrierPairPSK ( 2 , 7 , frameLen ) ;
frameLen = CheckCarrierPairPSK ( 3 , 8 , frameLen ) ;
frameLen = CheckCarrierPairPSK ( 4 , 9 , frameLen ) ;
return ;
}
// Non -robust
frameLen = 0 ;
for ( i = 0 ; i < intNumCar ; i + + )
{
Decode1CarPSK ( i , FALSE ) ;
frameLen + = CorrectRawDataWithRS ( & bytFrameData [ i ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , i ) ;
}
// If variable length packet frame header we only have header - leave rx running
if ( intFrameType = = PktFrameHeader )
{
State = SearchingForLeader ;
// Save any unused samples
if ( intFilteredMixedSamplesLength > 0 & & Start > 0 )
memmove ( intFilteredMixedSamples ,
& intFilteredMixedSamples [ Start ] , intFilteredMixedSamplesLength * 2 ) ;
return ;
}
# ifdef MEMORYARQ
for ( Carrier = 0 ; Carrier < intNumCar ; Carrier + + )
{
if ( ! CarrierOk [ Carrier ] )
{
// Decode error - save data for MEM ARQ
SavePSKSamples ( Carrier ) ;
if ( intSumCounts [ Carrier ] > 1 )
{
Decode1CarQAM ( Carrier ) ; // try to decode based on the WeightedAveragePhases
MemARQRetries + + ;
}
}
}
if ( MemARQRetries )
{
// We've retryed to decode - see if ok now
int OKNow = TRUE ;
Debugprintf ( " DemodPSK retry RS on MEM ARQ Corrected frames " ) ;
frameLen = 0 ;
for ( Carrier = 0 ; Carrier < intNumCar ; Carrier + + )
{
frameLen + = CorrectRawDataWithRS ( bytFrameData [ Carrier ] , bytData , intDataLen , intRSLen , intFrameType , Carrier ) ;
if ( CarrierOk [ Carrier ] = = 0 )
OKNow = FALSE ;
}
if ( OKNow & & AccumulateStats )
intGoodPSKSummationDecodes + + ;
}
# endif
}
return ;
}
// Function to demodulate one carrier for all PSK frame types
int Demod1CarPSKChar ( int Start , int Carrier )
{
// Converts intSample to an array of differential phase and magnitude values for the Specific Carrier Freq
// intPtr should be pointing to the approximate start of the first reference/training symbol (1 of 3)
// intPhase() is an array of phase values (in milliradians range of 0 to 6283) for each symbol
// intMag() is an array of Magnitude values (not used in PSK decoding but for constellation plotting or QAM decoding)
// Objective is to use Minimum Phase Error Tracking to maintain optimum pointer position
// This is called for one DMA buffer of samples (normally 1200)
float dblReal , dblImag ;
int intMiliRadPerSample = floatCarFreq * M_PI / 6 ;
int i ;
int intNumOfSymbols = intPSKMode ;
int origStart = Start ; ;
if ( CarrierOk [ Carrier ] ) // Already decoded this carrier?
{
intPhasesLen + = intNumOfSymbols ;
return intSampPerSym * intNumOfSymbols ;
}
for ( i = 0 ; i < intNumOfSymbols ; i + + )
{
GoertzelRealImag ( intFilteredMixedSamples , Start , intNforGoertzel [ Carrier ] , dblFreqBin [ Carrier ] , & dblReal , & dblImag ) ;
// GoertzelRealImagHann120(intFilteredMixedSamples, Start, intNforGoertzel[Carrier], dblFreqBin[Carrier], &dblReal, &dblImag);
intMags [ Carrier ] [ intPhasesLen ] = sqrtf ( powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ) ;
intPSKPhase_0 [ Carrier ] = - 1000 * atan2f ( dblImag , dblReal ) ;
intPhases [ Carrier ] [ intPhasesLen ] = ( ComputeAng1_Ang2 ( intPSKPhase_0 [ Carrier ] , intPSKPhase_1 [ Carrier ] ) ) ;
/*
if ( Carrier = = 0 )
{
Corrections = Track1CarPSK ( floatCarFreq , strMod , atan2f ( dblImag , dblReal ) , FALSE ) ;
if ( Corrections ! = 0 )
{
Start + = Corrections ;
if ( intCP [ i ] = = 0 )
GoertzelRealImagHanning ( intFilteredMixedSamples , Start , intNforGoertzel [ Carrier ] , dblFreqBin [ Carrier ] , & dblReal , & dblImag ) ;
else
GoertzelRealImag ( intFilteredMixedSamples , Start + intCP [ Carrier ] , intNforGoertzel [ Carrier ] , dblFreqBin [ Carrier ] , & dblReal , & dblImag ) ;
intPSKPhase_0 [ Carrier ] = 1000 * atan2f ( dblImag , dblReal ) ;
}
}
*/
intPSKPhase_1 [ Carrier ] = intPSKPhase_0 [ Carrier ] ;
intPhasesLen + + ;
Start + = intSampPerSym ;
}
if ( AccumulateStats )
intPSKSymbolCnt + = intNumOfSymbols ;
return ( Start - origStart ) ; // Symbols we've consumed
}
VOID InitDemodQAM ( )
{
// Called at start of frame
int i ;
float dblPhase , dblReal , dblImag ;
intPSKMode = 8 ; // 16QAM uses 8 PSK
dblPhaseInc = 2 * M_PI * 1000 / 8 ;
intPhasesLen = 0 ;
PSKInitDone = TRUE ;
intSampPerSym = 120 ;
if ( intNumCar = = 1 )
floatCarFreq = 1500 ;
else
floatCarFreq = 1400 + ( intNumCar / 2 ) * 200 ; // start at the highest carrier freq which is actually the lowest transmitted carrier due to Reverse sideband mixing
for ( i = 0 ; i < intNumCar ; i + + )
{
// Only 100 Hz for QAM
intCP [ i ] = 0 ;
intNforGoertzel [ i ] = 120 ;
dblFreqBin [ i ] = floatCarFreq / 100 ;
// Get initial Reference Phase
GoertzelRealImagHanning ( intFilteredMixedSamples , intCP [ i ] , intNforGoertzel [ i ] , dblFreqBin [ i ] , & dblReal , & dblImag ) ;
dblPhase = atan2f ( dblImag , dblReal ) ;
// Set initial mag from Reference Phase (which should be full power)
intCarMagThreshold [ i ] = sqrtf ( powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ) ;
intCarMagThreshold [ i ] * = 0.75 ;
Track1CarPSK ( floatCarFreq , 8 , TRUE , FALSE , dblPhase , TRUE ) ;
intPSKPhase_1 [ i ] = 1000 * dblPhase ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
}
}
int Demod1CarQAMChar ( int Start , int Carrier ) ;
// Function to average two angles using magnitude weighting
short WeightedAngleAvg ( short intAng1 , short intAng2 )
{
// Ang1 and Ang 2 are in the range of -3142 to + 3142 (miliradians)
// works but should come up with a routine that avoids Sin, Cos, Atan2
// Modified in Rev 0.3.5.1 to "weight" averaging by intMag1 and intMag2 (why!!!)
float dblSumX , dblSumY ;
dblSumX = cosf ( intAng1 / 1000.0 ) + cosf ( intAng2 / 1000.0 ) ;
dblSumY = sinf ( intAng1 / 1000.0 ) + sinf ( intAng2 / 1000.0 ) ;
return ( 1000 * atan2f ( dblSumY , dblSumX ) ) ;
}
# ifdef MEMORYARQ
void SaveQAMSamples ( int i )
{
int m ;
if ( intSumCounts [ i ] = = 0 )
{
// First try - initialize Sum counts Phase average and Mag Average
for ( m = 0 ; m < intPhasesLen ; m + + )
{
intCarPhaseAvg [ i ] [ m ] = intPhases [ i ] [ m ] ;
intCarMagAvg [ i ] [ m ] = intMags [ i ] [ m ] ;
}
}
else
{
for ( m = 0 ; m < intPhasesLen ; m + + )
{
intCarPhaseAvg [ i ] [ m ] = WeightedAngleAvg ( intCarPhaseAvg [ i ] [ m ] , intPhases [ i ] [ m ] ) ;
intPhases [ i ] [ m ] = intCarPhaseAvg [ i ] [ m ] ;
// Use simple weighted average for Mags
intCarMagAvg [ i ] [ m ] = ( intCarMagAvg [ i ] [ m ] * intSumCounts [ i ] + intMags [ i ] [ m ] ) / ( intSumCounts [ i ] + 1 ) ;
intMags [ i ] [ m ] = intCarMagAvg [ i ] [ m ] ;
}
}
intSumCounts [ i ] + + ;
}
void SavePSKSamples ( int i )
{
int m ;
if ( intSumCounts [ i ] = = 0 )
{
// First try - initialize Sum counts Phase average and Mag Average
for ( m = 0 ; m < intPhasesLen ; m + + )
{
intCarPhaseAvg [ i ] [ m ] = intPhases [ i ] [ m ] ;
}
}
else
{
for ( m = 0 ; m < intPhasesLen ; m + + )
{
intCarPhaseAvg [ i ] [ m ] = WeightedAngleAvg ( intCarPhaseAvg [ i ] [ m ] , intPhases [ i ] [ m ] ) ;
intPhases [ i ] [ m ] = intCarPhaseAvg [ i ] [ m ] ;
}
}
intSumCounts [ i ] + + ;
}
# endif
int CheckCarrierPair ( int Base , int Dup , int frameLen )
{
int i , Len ;
Debugprintf ( " DemodQAMR Carriers %d and %d " , Base , Dup ) ;
Decode1CarQAM ( Base ) ;
Len = CorrectRawDataWithRS ( & bytFrameData [ Base ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , Base ) ;
if ( CarrierOk [ Base ] )
{
// No need to decode 2nd
CarrierOk [ Dup ] = 1 ; // So FrameOk test is passed
Debugprintf ( " DemodQAMR Returning Len %d " , Len ) ;
return Len + frameLen ;
}
Debugprintf ( " DemodQAMR Carrier %d bad, trying %d " , Base , Dup ) ;
Decode1CarQAM ( Dup ) ; // Decode Dup carrier
Len = CorrectRawDataWithRS ( & bytFrameData [ Dup ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , Base ) ; // Save as carrier 1
if ( CarrierOk [ Base ] )
{
CarrierOk [ Dup ] = 1 ; // So FrameOk test is passed
bytFrameData [ Base ] [ 0 ] = Len ;
memcpy ( & bytFrameData [ Base ] [ 1 ] , & bytData [ frameLen ] , Len ) ; // Any retry will use first copy without new decode
Debugprintf ( " DemodQAMR Returning Len %d " , Len ) ;
return Len + frameLen ;
}
// Try to average phases for the two carriers
Debugprintf ( " DemodQAMR both bad, trying average " ) ;
for ( i = 0 ; i < intPhasesLen ; i + + )
{
intPhases [ Base ] [ i ] = WeightedAngleAvg ( intPhases [ Base ] [ i ] , intPhases [ Dup ] [ i ] ) ;
intMags [ Base ] [ i ] = ( intMags [ Base ] [ i ] + intMags [ Dup ] [ i ] ) / 2 ;
}
// Try decode again on averages
Decode1CarQAM ( Base ) ;
Len = CorrectRawDataWithRS ( & bytFrameData [ Base ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , Base ) ;
if ( CarrierOk [ Base ] )
CarrierOk [ Dup ] = 1 ; // So FrameOk test is passed
Debugprintf ( " DemodQAMR Returning Len %d " , Len ) ;
return Len + frameLen ;
}
BOOL DemodQAM ( )
{
int Used = 0 ;
int Start = 0 ;
int i , MemARQOk = 0 ;
// We can't wait for the full frame as we don't have enough RAM, so
// we do one DMA Buffer at a time, until we run out or end of frame
// Only continue if we have enough samples
while ( State = = AcquireFrame )
{
if ( intFilteredMixedSamplesLength < 8 * intSampPerSym + 10 ) // allow for a few phase corrections
{
// Move any unprocessessed data down buffer
// (while checking process - will use cyclic buffer eventually
if ( intFilteredMixedSamplesLength > 0 )
memmove ( intFilteredMixedSamples ,
& intFilteredMixedSamples [ Start ] , intFilteredMixedSamplesLength * 2 ) ;
return FALSE ;
}
if ( PSKInitDone = = 0 ) // First time through
{
if ( intFilteredMixedSamplesLength < 9 * intSampPerSym + 10 )
return FALSE ; // Wait for at least 2 chars worth
InitDemodQAM ( ) ;
intFilteredMixedSamplesLength - = intSampPerSym ;
Start + = intSampPerSym ;
}
// If this is a multicarrier mode, we must call the
// decode char routine for each carrier
if ( intNumCar = = 1 )
floatCarFreq = 1500 ;
else
floatCarFreq = 1400 + ( intNumCar / 2 ) * 200 ; // start at the highest carrier freq which is actually the lowest transmitted carrier due to Reverse sideband mixing
Used = Demod1CarQAMChar ( Start , 0 ) ; // demods 2 phase values - enough for one char
if ( intNumCar > 1 )
{
intPhasesLen - = 2 ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar ( Start , 1 ) ;
}
if ( intNumCar > 2 )
{
intPhasesLen - = 2 ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar ( Start , 2 ) ;
intPhasesLen - = 2 ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar ( Start , 3 ) ;
}
if ( intNumCar > 4 )
{
intPhasesLen - = 2 ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar ( Start , 4 ) ;
intPhasesLen - = 2 ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar ( Start , 5 ) ;
intPhasesLen - = 2 ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar ( Start , 6 ) ;
intPhasesLen - = 2 ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar ( Start , 7 ) ;
intPhasesLen - = 2 ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar ( Start , 8 ) ;
intPhasesLen - = 2 ;
floatCarFreq - = 200 ; // Step through each carrier Highest to lowest which is equivalent to lowest to highest before RSB mixing.
Demod1CarQAMChar ( Start , 9 ) ;
}
SymbolsLeft - - ; // number still to decode - we've done one
Start + = Used ;
intFilteredMixedSamplesLength - = Used ;
if ( SymbolsLeft < = 0 )
{
// Frame complete - decode it
DecodeCompleteTime = Now ;
// CorrectPhaseForTuningOffset(&intPhases[0][0], intPhasesLen, strMod);
// if (intNumCar > 1)
// CorrectPhaseForTuningOffset(&intPhases[1][0], intPhasesLen, strMod);
if ( intNumCar > 2 )
{
// CorrectPhaseForTuningOffset(&intPhases[2][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[3][0], intPhasesLen, strMod);
}
if ( intNumCar > 4 )
{
// CorrectPhaseForTuningOffset(&intPhases[4][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[5][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[6][0], intPhasesLen, strMod);
// CorrectPhaseForTuningOffset(&intPhases[7][0], intPhasesLen, strMod);
}
intLastRcvdFrameQuality = UpdatePhaseConstellation ( & intPhases [ intNumCar - 1 ] [ 0 ] , & intMags [ intNumCar - 1 ] [ 0 ] , 8 , TRUE , FALSE ) ;
// prepare for next so we can exit when we have finished decode
DiscardOldSamples ( ) ;
ClearAllMixedSamples ( ) ;
State = SearchingForLeader ;
if ( strchr ( strMod , ' R ' ) )
{
// Robust Mode - data is repeated (1-2 or 1-6, 2-7, etc
if ( intNumCar = = 2 )
{
frameLen = CheckCarrierPair ( 0 , 1 , 0 ) ;
return TRUE ;
}
//Only have 2 or 10 (500 or 2500 modes)
frameLen = CheckCarrierPair ( 0 , 5 , 0 ) ;
frameLen = CheckCarrierPair ( 1 , 6 , frameLen ) ;
frameLen = CheckCarrierPair ( 2 , 7 , frameLen ) ;
frameLen = CheckCarrierPair ( 3 , 8 , frameLen ) ;
frameLen = CheckCarrierPair ( 4 , 9 , frameLen ) ;
return TRUE ;
}
// Non -robust
Decode1CarQAM ( 0 ) ;
frameLen = CorrectRawDataWithRS ( & bytFrameData [ 0 ] [ 0 ] , bytData , intDataLen , intRSLen , intFrameType , 0 ) ;
if ( intNumCar > 1 )
{
Decode1CarQAM ( 1 ) ;
frameLen + = CorrectRawDataWithRS ( & bytFrameData [ 1 ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , 1 ) ;
}
if ( intNumCar > 2 )
{
Decode1CarQAM ( 2 ) ;
Decode1CarQAM ( 3 ) ;
frameLen + = CorrectRawDataWithRS ( & bytFrameData [ 2 ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , 2 ) ;
frameLen + = CorrectRawDataWithRS ( & bytFrameData [ 3 ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , 3 ) ;
}
if ( intNumCar > 4 )
{
Decode1CarQAM ( 4 ) ;
Decode1CarQAM ( 5 ) ;
Decode1CarQAM ( 6 ) ;
Decode1CarQAM ( 7 ) ;
Decode1CarQAM ( 8 ) ;
Decode1CarQAM ( 9 ) ;
frameLen + = CorrectRawDataWithRS ( & bytFrameData [ 4 ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , 4 ) ;
frameLen + = CorrectRawDataWithRS ( & bytFrameData [ 5 ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , 5 ) ;
frameLen + = CorrectRawDataWithRS ( & bytFrameData [ 6 ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , 6 ) ;
frameLen + = CorrectRawDataWithRS ( & bytFrameData [ 7 ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , 7 ) ;
frameLen + = CorrectRawDataWithRS ( & bytFrameData [ 8 ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , 8 ) ;
frameLen + = CorrectRawDataWithRS ( & bytFrameData [ 9 ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , 9 ) ;
}
// Check Data
if ( memcmp ( CarrierOk , Good , intNumCar ) = = 0 )
return TRUE ;
// Bad decode if we have Memory ARQ try it
# ifdef MEMORYARQ
for ( i = 0 ; i < intNumCar ; i + + )
{
if ( ! CarrierOk [ i ] & & intFrameType ! = PktFrameHeader )
{
// Decode error - save data for MEM ARQ
SaveQAMSamples ( i ) ;
if ( intSumCounts [ 0 ] > 1 )
{
MemARQOk = 1 ;
Decode1CarQAM ( i ) ; // try to decode based on the WeightedAveragePhases
}
}
}
if ( MemARQOk = = 0 ) // Havent averaged yet
return TRUE ;
// We've tried to correct - see if it worked
Debugprintf ( " DemodQAM Trying MEM ARQ " ) ;
// Non -robust
frameLen = 0 ;
for ( i = 0 ; i < intNumCar ; i + + )
{
frameLen + = CorrectRawDataWithRS ( & bytFrameData [ i ] [ 0 ] , & bytData [ frameLen ] , intDataLen , intRSLen , intFrameType , i ) ;
}
// Check Data
if ( memcmp ( CarrierOk , Good , intNumCar ) = = 0 )
{
Debugprintf ( " DemodQAM MEM ARQ Corrected frame " ) ;
intGoodQAMSummationDecodes + + ;
}
# endif
}
}
return TRUE ;
}
int Demod1CarQAMChar ( int Start , int Carrier )
{
// Converts intSample to an array of differential phase and magnitude values for the Specific Carrier Freq
// intPtr should be pointing to the approximate start of the first reference/training symbol (1 of 3)
// intPhase() is an array of phase values (in milliradians range of 0 to 6283) for each symbol
// intMag() is an array of Magnitude values (not used in PSK decoding but for constellation plotting or QAM decoding)
// Objective is to use Minimum Phase Error Tracking to maintain optimum pointer position
// This is called for one DMA buffer of samples (normally 1200)
float dblReal , dblImag ;
int intMiliRadPerSample = floatCarFreq * M_PI / 6 ;
int i ;
int intNumOfSymbols = 2 ;
int origStart = Start ; ;
if ( CarrierOk [ Carrier ] ) // Already decoded this carrier?
{
intPhasesLen + = intNumOfSymbols ;
return intSampPerSym * intNumOfSymbols ;
}
for ( i = 0 ; i < intNumOfSymbols ; i + + )
{
// GoertzelRealImag(intFilteredMixedSamples, Start + intCP[Carrier], intNforGoertzel[Carrier], dblFreqBin[Carrier], &dblReal, &dblImag);
GoertzelRealImagHanning ( intFilteredMixedSamples , Start + intCP [ Carrier ] , intNforGoertzel [ Carrier ] , dblFreqBin [ Carrier ] , & dblReal , & dblImag ) ;
intMags [ Carrier ] [ intPhasesLen ] = sqrtf ( powf ( dblReal , 2 ) + powf ( dblImag , 2 ) ) ;
intPSKPhase_0 [ Carrier ] = 1000 * atan2f ( dblImag , dblReal ) ;
intPhases [ Carrier ] [ intPhasesLen ] = - ( ComputeAng1_Ang2 ( intPSKPhase_0 [ Carrier ] , intPSKPhase_1 [ Carrier ] ) ) ;
/*
if ( Carrier = = 0 )
{
Corrections = Track1CarPSK ( floatCarFreq , strMod , atan2f ( dblImag , dblReal ) , FALSE ) ;
if ( Corrections ! = 0 )
{
Start + = Corrections ;
// GoertzelRealImag(intFilteredMixedSamples, Start + intCP[Carrier], intNforGoertzel[Carrier], dblFreqBin[Carrier], &dblReal, &dblImag);
GoertzelRealImagHanning ( intFilteredMixedSamples , Start + intCP [ Carrier ] , intNforGoertzel [ Carrier ] , dblFreqBin [ Carrier ] , & dblReal , & dblImag ) ;
intPSKPhase_0 [ Carrier ] = 1000 * atan2f ( dblImag , dblReal ) ;
}
}
*/
intPSKPhase_1 [ Carrier ] = intPSKPhase_0 [ Carrier ] ;
intPhasesLen + + ;
Start + = intSampPerSym ;
}
if ( AccumulateStats )
intQAMSymbolCnt + = intNumOfSymbols ;
return ( Start - origStart ) ; // Symbols we've consumed
}
extern int bytQDataInProcessLen ;
// function to decode one carrier from tones (used to decode from Averaged intToneMags)
BOOL Decode1Car4FSKFromTones ( UCHAR * bytData , int intToneMags )
{
// Decodes intToneMags() to an array of bytes
// Updates bytData() with decoded
/*
UCHAR bytSym ;
int intIndex ;
ReDim bytData ( intToneMags . Length \ 16 - 1 )
For i As Integer = 0 To bytData . Length - 1 ' For each data byte
intIndex = 16 * i
For j As Integer = 0 To 3 ' for each 4F SK symbol ( 2 bits ) in a byte
If intToneMags ( intIndex ) > intToneMags ( intIndex + 1 ) And intToneMags ( intIndex ) > intToneMags ( intIndex + 2 ) And intToneMags ( intIndex ) > intToneMags ( intIndex + 3 ) Then
bytSym = 0
ElseIf intToneMags ( intIndex + 1 ) > intToneMags ( intIndex ) And intToneMags ( intIndex + 1 ) > intToneMags ( intIndex + 2 ) And intToneMags ( intIndex + 1 ) > intToneMags ( intIndex + 3 ) Then
bytSym = 1
ElseIf intToneMags ( intIndex + 2 ) > intToneMags ( intIndex ) And intToneMags ( intIndex + 2 ) > intToneMags ( intIndex + 1 ) And intToneMags ( intIndex + 2 ) > intToneMags ( intIndex + 3 ) Then
bytSym = 2
Else
bytSym = 3
End If
bytData ( i ) = ( bytData ( i ) < < 2 ) + bytSym
intIndex + = 4
Next j
Next i
Return True
End Function ' Decode1Car4FSKFromTones
*/
return TRUE ;
}
/* ' Function to decode one carrier from tones (used to decode from Averaged intToneMags)
Private Function Decode1Car8FSKFromTones ( ByRef bytData ( ) As Byte , ByRef intToneMags ( ) As Int32 ) As Boolean
' Decodes intToneMags ( ) to an array of bytes
' Updates bytData ( ) with decoded
Dim bytSym As Byte
Dim intThreeBytes As Int32
ReDim bytData ( 3 * intToneMags . Length \ 64 - 1 )
Dim intMaxMag As Int32
For i As Integer = 0 To ( bytData . Length \ 3 ) - 1 ' For each group of 3 bytes data byte
intThreeBytes = 0
For j As Integer = 0 To 7 ' for each group of 8 symbols ( 24 bits )
intMaxMag = 0
For k As Integer = 0 To 7 ' for each of 8 possible tones per symbol
If intToneMags ( ( i * 64 ) + 8 * j + k ) > intMaxMag Then
intMaxMag = intToneMags ( ( i * 64 ) + 8 * j + k )
bytSym = k
End If
Next k
intThreeBytes = ( intThreeBytes < < 3 ) + bytSym
Next j
bytData ( 3 * i ) = ( intThreeBytes And & HFF0000 ) > > 16
bytData ( 3 * i + 1 ) = ( intThreeBytes And & HFF00 ) > > 8
bytData ( 3 * i + 2 ) = ( intThreeBytes And & HFF )
Next i
Return True
End Function ' Decode1Car8FSKFromTones
' Function to decode one carrier from tones ( used to decode from Averaged intToneMags )
Private Function Decode1Car16FSKFromTones ( ByRef bytData ( ) As Byte , ByRef intToneMags ( ) As Int32 ) As Boolean
' Decodes intToneMags ( ) to an array of bytes
' Updates bytData ( ) with decoded tones
Dim bytSym As Byte
Dim intMaxMag As Int32
ReDim bytData ( intToneMags . Length \ 32 - 1 )
For i As Integer = 0 To bytData . Length - 1 ' For each data byte
For j As Integer = 0 To 1 ' for each 16F SK symbol ( 4 bits ) in a byte
intMaxMag = 0
For k As Integer = 0 To 15
If intToneMags ( i * 32 + 16 * j + k ) > intMaxMag Then
intMaxMag = intToneMags ( i * 32 + 16 * j + k )
bytSym = k
End If
Next k
bytData ( i ) = ( bytData ( i ) < < 4 ) + bytSym
Next j
Next i
Return True
End Function ' Decode1Car16FSKFromTones
*/
// Subroutine to update the Busy detector when not displaying Spectrum or Waterfall (graphics disabled)
extern int LastBusyCheck ;
extern BOOL blnBusyStatus ;
int intWaterfallRow = 0 ;
void UpdateBusyDetector ( short * bytNewSamples )
{
float dblReF [ 1024 ] ;
float dblImF [ 1024 ] ;
float dblMag [ 206 ] ;
float dblMagAvg = 0 ;
int intTuneLineLow , intTuneLineHi , intDelta ;
int i ;
int BusyFlag ;
if ( ProtocolState > DISC ) // ' Only process busy when in DISC state
return ;
if ( State ! = SearchingForLeader )
return ; // only when looking for leader
if ( Now - LastBusyCheck < 100 )
return ;
LastBusyCheck = Now ;
FourierTransform ( 1024 , bytNewSamples , & dblReF [ 0 ] , & dblImF [ 0 ] , FALSE ) ;
for ( i = 0 ; i < 206 ; i + + )
{
// starting at ~300 Hz to ~2700 Hz Which puts the center of the signal in the center of the window (~1500Hz)
dblMag [ i ] = powf ( dblReF [ i + 25 ] , 2 ) + powf ( dblImF [ i + 25 ] , 2 ) ; // first pass
dblMagAvg + = dblMag [ i ] ;
}
// Not sure about this as we use variable bandwidth frames. For now use 500
intDelta = ( 500 / 2 + TuningRange ) / 11.719f ;
intTuneLineLow = max ( ( 103 - intDelta ) , 3 ) ;
intTuneLineHi = min ( ( 103 + intDelta ) , 203 ) ;
// At the moment we only get here what seaching for leader,
// but if we want to plot spectrum we should call
// it always
BusyFlag = BusyDetect3 ( dblMag , intTuneLineLow , intTuneLineHi ) ;
if ( BusyFlag = = 0 )
{
if ( BusyCount = = 0 )
blnBusyStatus = 0 ;
else
BusyCount - - ;
}
else
{
blnBusyStatus = 1 ;
BusyCount = 10 ; // Try delaying busy off a bit
}
if ( blnBusyStatus & & ! blnLastBusyStatus )
{
Debugprintf ( " BUSY TRUE " ) ;
}
// stcStatus.Text = "True"
// queTNCStatus.Enqueue(stcStatus)
// 'Debug.WriteLine("BUSY TRUE @ " & Format(DateTime.UtcNow, "HH:mm:ss"))
else if ( blnLastBusyStatus & & ! blnBusyStatus )
{
Debugprintf ( " BUSY FALSE " ) ;
}
blnLastBusyStatus = blnBusyStatus ;
}