1118 lines
31 KiB
C
1118 lines
31 KiB
C
// Sample Creation routines (encode and filter) for ARDOP Modem
|
|
|
|
#include "ARDOPC.h"
|
|
#include <math.h>
|
|
|
|
#define ARDOPBufferSize 12000 * 100
|
|
|
|
char CWIDMark[32] = "";
|
|
|
|
extern short ARDOPTXBuffer[4][ARDOPBufferSize]; // Enough to hold whole frame of samples
|
|
|
|
extern int ARDOPTXLen[4]; // Length of frame
|
|
extern int ARDOPTXPtr[4]; // Tx Pointer
|
|
|
|
extern int intSessionBW; // Negotiated speed
|
|
|
|
#pragma warning(disable : 4244) // Code does lots of float to int
|
|
|
|
FILE * fp1;
|
|
|
|
float dblQAMCarRatio = 1.0f / 1.765f; //Optimum for 8,8 circular constellation with no phase offset: (Dmin inner to inner = Dmin inner to outer)
|
|
|
|
#define MAX(x, y) ((x) > (y) ? (x) : (y))
|
|
|
|
// Function to generate the Two-tone leader and Frame Sync (used in all frame types)
|
|
|
|
extern short Dummy;
|
|
|
|
int intSoftClipCnt = 0;
|
|
BOOL SendingHeader200 = 0; // Set when sending header in 200 Hz Modes
|
|
|
|
void ARDOPFlush();
|
|
|
|
int intBW; // Requested connect speed
|
|
int intSessionBW; // Negotiated speed
|
|
UCHAR bytLastReceivedDataFrameType;
|
|
UCHAR bytLastARQSessionID;
|
|
int blnPending = 0;
|
|
int intSessionBW = 500;
|
|
|
|
void ARDOPSampleSink(short Sample);
|
|
extern CONST short int50BaudTwoToneLeaderTemplate[240]; // holds just 1 symbol (20 ms) of the leader
|
|
|
|
|
|
extern int TrailerLength;
|
|
|
|
void AddTrailer(int Chan)
|
|
{
|
|
int intAddedSymbols = 1 + TrailerLength / 10; // add 1 symbol + 1 per each 10 ms of MCB.Trailer
|
|
int i, k;
|
|
|
|
for (i = 1; i <= intAddedSymbols; i++)
|
|
{
|
|
for (k = 0; k < 120; k++)
|
|
{
|
|
ARDOPSampleSink(intQAM50bdCarTemplate[5][0][k % 60]);
|
|
}
|
|
}
|
|
}
|
|
|
|
extern int Number;
|
|
|
|
void ARDOPFlush(int Chan)
|
|
{
|
|
AddTrailer(Chan); // add the trailer.
|
|
ARDOPTXPtr[Chan] = 0;
|
|
ARDOPTXLen[Chan] = Number;
|
|
}
|
|
|
|
// Function to soft clip combined waveforms.
|
|
int SoftClip(int intInput)
|
|
{
|
|
if (intInput > 30000) // soft clip above/below 30000
|
|
{
|
|
intInput = 30000; //min(32700, 30000 + 20 * sqrt(intInput - 30000));
|
|
intSoftClipCnt += 1;
|
|
}
|
|
else if(intInput < -30000)
|
|
{
|
|
intInput = -30000; //max(-32700, -30000 - 20 * sqrt(-(intInput + 30000)));
|
|
intSoftClipCnt += 1;
|
|
}
|
|
|
|
if (intInput == 0)
|
|
intInput = 0;
|
|
|
|
|
|
return intInput;
|
|
}
|
|
|
|
|
|
void GetTwoToneLeaderWithSync(int intSymLen)
|
|
{
|
|
// Generate a 50 baud (20 ms symbol time) 2 tone leader
|
|
// leader tones used are 1475 and 1525 Hz.
|
|
|
|
int intSign = 1;
|
|
int i, j;
|
|
short intSample;
|
|
|
|
if ((intSymLen & 1) == 1)
|
|
intSign = -1;
|
|
|
|
for (i = 0; i < intSymLen; i++) //for the number of symbols needed (two symbols less than total leader length)
|
|
{
|
|
for (j = 0; j < 240; j++) // for 240 samples per symbol (50 baud)
|
|
{
|
|
if (i != (intSymLen - 1))
|
|
intSample = intSign * int50BaudTwoToneLeaderTemplate[j];
|
|
else
|
|
intSample = -intSign * int50BaudTwoToneLeaderTemplate[j];
|
|
|
|
ARDOPSampleSink(intSample);
|
|
}
|
|
intSign = -intSign;
|
|
}
|
|
}
|
|
|
|
void SendLeaderAndSYNC(UCHAR * bytEncodedBytes, int intLeaderLen)
|
|
{
|
|
int intLeaderLenMS;
|
|
int j, k, n;
|
|
UCHAR bytMask;
|
|
UCHAR bytSymToSend;
|
|
short intSample;
|
|
if (intLeaderLen == 0)
|
|
intLeaderLenMS = LeaderLength;
|
|
else
|
|
intLeaderLenMS = intLeaderLen;
|
|
|
|
// Create the leader
|
|
|
|
GetTwoToneLeaderWithSync(intLeaderLenMS / 20);
|
|
|
|
//Create the 8 symbols (16 bit) 50 baud 4FSK frame type with Implied SessionID
|
|
|
|
for(j = 0; j < 2; j++) // for the 2 bytes of the frame type
|
|
{
|
|
bytMask = 0x30;
|
|
|
|
for(k = 0; k < 4; k++) // for 4 symbols per byte (3 data + 1 parity)
|
|
{
|
|
if (k < 3)
|
|
bytSymToSend = (bytMask & bytEncodedBytes[j]) >> (2 * (2 - k));
|
|
else
|
|
bytSymToSend = ComputeTypeParity(bytEncodedBytes[j]);
|
|
|
|
for(n = 0; n < 240; n++)
|
|
{
|
|
// if ( ARQBandwidth == XB2500) // 2500 Hz
|
|
// {
|
|
// if (bytSymToSend < 2)
|
|
// intSample = (0.62 * intFSK50bdCarTemplate[4 + bytSymToSend][n]) + (0.62 * intFSK50bdCarTemplate[bytSymToSend][n]); // 4 is offset to center tones 1350, 1450, 1550, 1650Hz
|
|
// else
|
|
// intSample = (0.62 * intFSK50bdCarTemplate[4 + bytSymToSend][n]) + (0.62 * intFSK50bdCarTemplate[8 + bytSymToSend][n]); // 4 is offset to center tones 1350, 1450, 1550, 1650Hz, 8 is offset to tones 1800 and 1900
|
|
// }
|
|
// else
|
|
intSample = intFSK50bdCarTemplate[bytSymToSend + 4][n];
|
|
|
|
ARDOPSampleSink(intSample);
|
|
}
|
|
bytMask = bytMask >> 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mod4FSKDataAndPlay(unsigned char * bytEncodedBytes, int Len, int intLeaderLen, int Chan)
|
|
{
|
|
// Function to Modulate data encoded for 4FSK, create
|
|
// the 16 bit samples and send to sound interface
|
|
|
|
// Function works for 1, 2 or 4 simultaneous carriers
|
|
|
|
int intNumCar, intBaud, intDataLen, intRSLen, intDataPtr, intSampPerSym, intDataBytesPerCar;
|
|
BOOL blnOdd;
|
|
|
|
int intSample;
|
|
|
|
char strType[18] = "";
|
|
char strMod[16] = "";
|
|
|
|
UCHAR bytSymToSend, bytMask, bytMinQualThresh;
|
|
|
|
float dblCarScalingFactor;
|
|
int intLeaderLenMS;
|
|
int k, m, n;
|
|
UCHAR Type = bytEncodedBytes[0];
|
|
|
|
if (!FrameInfo(Type, &blnOdd, &intNumCar, strMod, &intBaud, &intDataLen, &intRSLen, &bytMinQualThresh, strType))
|
|
return;
|
|
|
|
if (strcmp(strMod, "4FSK") != 0)
|
|
return;
|
|
|
|
Debugprintf("Sending Frame Type %s", strType);
|
|
DrawTXFrame(strType);
|
|
|
|
if (bytEncodedBytes[0] == PktFrameHeader)
|
|
{
|
|
// Leader is 4FSK which needs 500 filter
|
|
|
|
if (pktBW[pktMode] < 1000)
|
|
initFilter(500, 1500, Chan);
|
|
else
|
|
initFilter(2500, 1500, Chan);
|
|
}
|
|
else
|
|
{
|
|
if (ARQBandwidth == XB200)
|
|
initFilter(200, 1500, Chan);
|
|
else if (intNumCar == 1)
|
|
initFilter(500, 1500, Chan);
|
|
else
|
|
initFilter(2500, 1500, Chan);
|
|
}
|
|
|
|
if (intLeaderLen == 0)
|
|
intLeaderLenMS = LeaderLength;
|
|
else
|
|
intLeaderLenMS = intLeaderLen;
|
|
|
|
switch (intBaud)
|
|
{
|
|
case 50:
|
|
|
|
intSampPerSym = 240;
|
|
break;
|
|
|
|
case 100:
|
|
|
|
intSampPerSym = 120;
|
|
}
|
|
|
|
intDataBytesPerCar = (Len - 2) / intNumCar; // We queue the samples here, so dont copy below
|
|
|
|
SendLeaderAndSYNC(bytEncodedBytes, intLeaderLen);
|
|
|
|
intDataPtr = 2;
|
|
|
|
Reenter:
|
|
|
|
switch (intNumCar)
|
|
{
|
|
case 1: // use carriers 0-3
|
|
|
|
dblCarScalingFactor = 1.0; // (scaling factors determined emperically to minimize crest factor)
|
|
|
|
for (m = 0; m < intDataBytesPerCar; m++) // For each byte of input data
|
|
{
|
|
bytMask = 0xC0; // Initialize mask each new data byte
|
|
|
|
for (k = 0; k < 4; k++) // for 4 symbol values per byte of data
|
|
{
|
|
bytSymToSend = (bytMask & bytEncodedBytes[intDataPtr]) >> (2 * (3 - k)); // Values 0-3
|
|
|
|
for (n = 0; n < intSampPerSym; n++) // Sum for all the samples of a symbols
|
|
{
|
|
if (intBaud == 50)
|
|
{
|
|
if (intSessionBW == 200 && (bytSymToSend == 0 || bytSymToSend == 3))
|
|
// This scales down the two outer tones in 200 Hz mode to restrict bandwidth slightly
|
|
intSample = 0.7f * intFSK50bdCarTemplate[4 + bytSymToSend][n]; //4 is offset to center tones 1350, 1450, 1550, 1650Hz
|
|
else
|
|
intSample = intFSK50bdCarTemplate[4 + bytSymToSend][n]; //4 is offset to center tones 1350, 1450, 1550, 1650Hz
|
|
}
|
|
else if (intBaud == 100) // Used for OFDMACK
|
|
{
|
|
intSample = intFSK100bdCarTemplate[bytSymToSend][n];
|
|
}
|
|
ARDOPSampleSink(intSample);
|
|
}
|
|
|
|
bytMask = bytMask >> 2;
|
|
}
|
|
intDataPtr += 1;
|
|
}
|
|
|
|
if (Type == PktFrameHeader)
|
|
{
|
|
|
|
// just sent packet header. Send rest in current mode
|
|
// Assumes we are using 4FSK for Packet Header
|
|
|
|
bytEncodedBytes[0] = Type = PktFrameData; // Prevent reentry
|
|
|
|
strcpy(strMod, &pktMod[pktMode][0]);
|
|
intDataBytesPerCar = pktDataLen + pktRSLen + 3;
|
|
intDataPtr = 11; // Over Header
|
|
intNumCar = pktCarriers[pktMode];
|
|
|
|
// This assumes Packet Data is sent as PSK/QAM
|
|
|
|
switch (intNumCar)
|
|
{
|
|
case 1:
|
|
// intCarStartIndex = 4;
|
|
dblCarScalingFactor = 1.0f; // Starting at 1500 Hz (scaling factors determined emperically to minimize crest factor) TODO: needs verification
|
|
break;
|
|
case 2:
|
|
// intCarStartIndex = 3;
|
|
dblCarScalingFactor = 0.53f; // Starting at 1400 Hz
|
|
break;
|
|
case 4:
|
|
// intCarStartIndex = 2;
|
|
dblCarScalingFactor = 0.29f; // Starting at 1200 Hz
|
|
break;
|
|
case 8:
|
|
// intCarStartIndex = 0;
|
|
dblCarScalingFactor = 0.17f; // Starting at 800 Hz
|
|
}
|
|
|
|
// Reenter to send rest of variable length packet frame
|
|
|
|
if (pktFSK[pktMode])
|
|
goto Reenter;
|
|
else
|
|
ModPSKDataAndPlay(bytEncodedBytes, 0, 0, Chan);
|
|
return;
|
|
}
|
|
|
|
ARDOPFlush(Chan);
|
|
|
|
break;
|
|
|
|
case 2: // use carriers 0-3 and 8-11 (50 baud only)
|
|
|
|
dblCarScalingFactor = 0.6f; // (scaling factors determined emperically to minimize crest factor)
|
|
|
|
for (m = 0; m < intDataBytesPerCar; m++) // For each byte of input data
|
|
{
|
|
bytMask = 0xC0; // Initialize mask each new data byte
|
|
|
|
for (k = 0; k < 4; k++) // for 4 symbol values per byte of data
|
|
{
|
|
for (n = 0; n < intSampPerSym; n++) // for all the samples of a symbol for 2 carriers
|
|
{
|
|
//' First carrier
|
|
|
|
bytSymToSend = (bytMask & bytEncodedBytes[intDataPtr]) >> (2 * (3 - k)); // Values 0-3
|
|
intSample = intFSK50bdCarTemplate[bytSymToSend][n];
|
|
// Second carrier
|
|
|
|
bytSymToSend = (bytMask & bytEncodedBytes[intDataPtr + intDataBytesPerCar]) >> (2 * (3 - k)); // Values 0-3
|
|
intSample = SoftClip(dblCarScalingFactor * (intSample + intFSK50bdCarTemplate[8 + bytSymToSend][n]));
|
|
|
|
ARDOPSampleSink(intSample);
|
|
}
|
|
bytMask = bytMask >> 2;
|
|
}
|
|
intDataPtr += 1;
|
|
}
|
|
|
|
ARDOPFlush(Chan);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Function to extract an 8PSK symbol from an encoded data array
|
|
|
|
|
|
UCHAR GetSym8PSK(int intDataPtr, int k, int intCar, UCHAR * bytEncodedBytes, int intDataBytesPerCar)
|
|
{
|
|
int int3Bytes = bytEncodedBytes[intDataPtr + intCar * intDataBytesPerCar];
|
|
// int intMask = 7;
|
|
int intSym;
|
|
UCHAR bytSym;
|
|
|
|
int3Bytes = int3Bytes << 8;
|
|
int3Bytes += bytEncodedBytes[intDataPtr + intCar * intDataBytesPerCar + 1];
|
|
int3Bytes = int3Bytes << 8;
|
|
int3Bytes += bytEncodedBytes[intDataPtr + intCar * intDataBytesPerCar + 2]; // now have 3 bytes, 24 bits or 8 8PSK symbols
|
|
// intMask = intMask << (3 * (7 - k));
|
|
intSym = int3Bytes >> (3 * (7 - k));
|
|
bytSym = intSym & 7; //(intMask && int3Bytes) >> (3 * (7 - k));
|
|
|
|
return bytSym;
|
|
}
|
|
|
|
|
|
|
|
// Function to Modulate data encoded for PSK and 16QAM, create
|
|
// the 16 bit samples and send to sound interface
|
|
|
|
|
|
void ModPSKDataAndPlay(unsigned char * bytEncodedBytes, int Len, int intLeaderLen, int Chan)
|
|
{
|
|
int intNumCar, intBaud, intDataLen, intRSLen, intDataPtr, intSampPerSym, intDataBytesPerCar;
|
|
BOOL blnOdd;
|
|
int Type = bytEncodedBytes[0];
|
|
|
|
int intSample;
|
|
char strType[18] = "";
|
|
char strMod[16] = "";
|
|
UCHAR bytSym, bytSymToSend, bytMinQualThresh;
|
|
float dblCarScalingFactor;
|
|
int intLeaderLenMS;
|
|
int i, j, k, l = 4, n;
|
|
int intCarStartIndex;
|
|
int intPeakAmp;
|
|
int intCarIndex;
|
|
BOOL QAM = 0;
|
|
|
|
UCHAR bytLastSym[43]; // = {0}; // Holds the last symbol sent (per carrier). bytLastSym(4) is 1500 Hz carrier (only used on 1 carrier modes)
|
|
|
|
if (!FrameInfo(Type, &blnOdd, &intNumCar, strMod, &intBaud, &intDataLen, &intRSLen, &bytMinQualThresh, strType))
|
|
return;
|
|
|
|
intDataBytesPerCar = (Len - 2) / intNumCar; // We queue the samples here, so dont copy below
|
|
|
|
// These new scaling factor combined with soft clipping to provide near optimum scaling Jan 6, 2018
|
|
// The Test form was changed to calculate the Peak power to RMS power (PAPR) of the test waveform and count the number of "soft clips" out of ~ 50,000 samples.
|
|
// These values arrived at emperically using the Test form (Quick brown fox message) to minimize PAPR at a minor decrease in maximum constellation quality
|
|
|
|
if (strstr(strMod, "16QAM"))
|
|
{
|
|
// QAM Modes
|
|
|
|
QAM = 1;
|
|
l = 2; // 2 symbols per byte
|
|
|
|
switch (intNumCar)
|
|
{
|
|
case 1:
|
|
intCarStartIndex = 5;
|
|
dblCarScalingFactor = 2.0f; // Starting at 1500 Hz Selected to give < 13% clipped values yielding a PAPR = 1.6 Constellation Quality >98
|
|
break;
|
|
case 2:
|
|
intCarStartIndex = 4;
|
|
dblCarScalingFactor = 1.0f;
|
|
break;
|
|
case 10:
|
|
intCarStartIndex = 0;
|
|
dblCarScalingFactor = 0.4f;
|
|
}
|
|
}
|
|
else // 4PSK
|
|
{
|
|
switch (intNumCar)
|
|
{
|
|
case 1:
|
|
intCarStartIndex = 5;
|
|
dblCarScalingFactor = 2.0f; // Starting at 1500 Hz Selected to give < 13% clipped values yielding a PAPR = 1.6 Constellation Quality >98
|
|
break;
|
|
case 2:
|
|
intCarStartIndex = 4;
|
|
dblCarScalingFactor = 1.2f;
|
|
break;
|
|
case 10:
|
|
intCarStartIndex = 0;
|
|
dblCarScalingFactor = 0.35f;
|
|
}
|
|
}
|
|
|
|
if (intBaud == 50)
|
|
intSampPerSym = 240;
|
|
else
|
|
intSampPerSym = 120;
|
|
|
|
if (Type == PktFrameData)
|
|
{
|
|
intDataBytesPerCar = pktDataLen + pktRSLen + 3;
|
|
intDataPtr = 11; // Over Header
|
|
goto PktLoopBack;
|
|
}
|
|
|
|
Debugprintf("Sending Frame Type %s", strType);
|
|
DrawTXFrame(strType);
|
|
|
|
if (intNumCar == 1)
|
|
initFilter(200, 1500, Chan);
|
|
else if (intNumCar == 2 || intNumCar == 9)
|
|
initFilter(500, 1500, Chan);
|
|
else
|
|
initFilter(2500, 1500, Chan);
|
|
|
|
if (intLeaderLen == 0)
|
|
intLeaderLenMS = LeaderLength;
|
|
else
|
|
intLeaderLenMS = intLeaderLen;
|
|
|
|
intSoftClipCnt = 0;
|
|
|
|
// Create the leader
|
|
|
|
SendLeaderAndSYNC(bytEncodedBytes, intLeaderLen);
|
|
SendingHeader200 = FALSE;
|
|
|
|
intPeakAmp = 0;
|
|
|
|
intDataPtr = 2; // initialize pointer to start of data.
|
|
|
|
PktLoopBack: // Reenter here to send rest of variable length packet frame
|
|
|
|
|
|
// Now create a reference symbol for each carrier
|
|
|
|
// We have to do each carrier for each sample, as we write
|
|
// the sample immediately
|
|
|
|
for (n = 0; n < intSampPerSym; n++) // Sum for all the samples of a symbols
|
|
{
|
|
intSample = 0;
|
|
intCarIndex = intCarStartIndex; // initialize to correct starting carrier
|
|
|
|
for (i = 0; i < intNumCar; i++) // across all carriers
|
|
{
|
|
bytSymToSend = 0; // using non 0 causes error on first data byte 12/8/2014 ...Values 0-3 not important (carries no data). (Possible chance for Crest Factor reduction?)
|
|
bytLastSym[intCarIndex] = 0;
|
|
|
|
|
|
intSample += intQAM50bdCarTemplate[intCarIndex][0][n % 120];
|
|
|
|
intCarIndex++;
|
|
if (intCarIndex == 5)
|
|
intCarIndex = 6; // skip over 1500 Hz for multi carrier modes (multi carrier modes all use even hundred Hz tones)
|
|
}
|
|
intSample = SoftClip(intSample * 0.5 * dblCarScalingFactor);
|
|
ARDOPSampleSink(intSample);
|
|
}
|
|
|
|
// End of reference phase generation
|
|
|
|
// Unlike ARDOP_WIN we send samples as they are created,
|
|
// so we loop through carriers, then data bytes
|
|
|
|
for (j = 0; j < intDataBytesPerCar; j++) // for each referance and data symbol
|
|
{
|
|
// Loop through each symbol of byte (4 for PSK 2 for QAM
|
|
|
|
for (k = 0; k < l; k++)
|
|
{
|
|
for (n = 0; n < intSampPerSym; n++)
|
|
{
|
|
intSample = 0;
|
|
intCarIndex = intCarStartIndex; // initialize the carrrier index
|
|
|
|
for (i = 0; i < intNumCar; i++) // across all active carriers
|
|
{
|
|
if (QAM == 0)
|
|
{
|
|
bytSym = (bytEncodedBytes[intDataPtr + i * intDataBytesPerCar] >> (2 * (3 - k))) & 3;
|
|
bytSymToSend = ((bytLastSym[intCarIndex] + bytSym) & 3); // Values 0-3
|
|
|
|
if (bytSymToSend < 2) // This uses the symmetry of the symbols to reduce the table size by a factor of 2
|
|
intSample += intQAM50bdCarTemplate[intCarIndex][2 * bytSymToSend][n % 120]; // double the symbol value during template lookup for 4PSK. (skips over odd PSK 8 symbols)
|
|
else if (bytSymToSend < 4)
|
|
intSample -= intQAM50bdCarTemplate[intCarIndex][2 * (bytSymToSend - 2)][n % 120]; // subtract 2 from the symbol value before doubling and subtract value of table
|
|
|
|
}
|
|
else
|
|
{
|
|
// For 16QAM the angle is sent differential but the amplitude is sent as is for the symbol...verified 4/20 2018
|
|
|
|
bytSym = (bytEncodedBytes[intDataPtr + i * intDataBytesPerCar] >> (4 * (1 - k))) & 15;
|
|
bytSymToSend = ((bytLastSym[intCarIndex] & 7) + (bytSym & 7) & 7); // Compute the differential phase to send
|
|
bytSymToSend = bytSymToSend | (bytSym & 8); // add in the amplitude bit directly from symbol
|
|
|
|
// 4bits/symbol (use table symbol values 0, 1, 2, 3, -0, -1, -2, -3) and modulate amplitude with MSB
|
|
|
|
if (bytSymToSend < 4)// This uses the symmetry of the symbols to reduce the table size by a factor of 2
|
|
intSample += intQAM50bdCarTemplate[intCarIndex][bytSymToSend][n % 120]; // double the symbol value during template lookup for 4PSK. (skips over odd PSK 8 symbols)
|
|
else if (bytSymToSend < 8)
|
|
intSample -= intQAM50bdCarTemplate[intCarIndex][(bytSymToSend - 4)][n % 120]; // subtract 4 from the symbol value before doubling and subtract value of table
|
|
else if (bytSymToSend < 12)
|
|
intSample += dblQAMCarRatio * intQAM50bdCarTemplate[intCarIndex][bytSymToSend - 8][n % 120]; // subtract 4 from the symbol value before doubling and subtract value of table
|
|
else
|
|
intSample -= dblQAMCarRatio * intQAM50bdCarTemplate[intCarIndex][bytSymToSend - 12][n % 120]; // subtract 4 from the symbol value before doubling and subtract value of table
|
|
}
|
|
|
|
if (n == intSampPerSym - 1) // Last sample?
|
|
bytLastSym[intCarIndex] = bytSymToSend;
|
|
|
|
intCarIndex += 1;
|
|
if (intCarIndex == 5)
|
|
intCarIndex = 6; // skip over 1500 Hz carrier for multicarrier modes
|
|
}
|
|
|
|
// Done all carriers - send sample
|
|
|
|
intSample = SoftClip(intSample * 0.5 * dblCarScalingFactor);
|
|
ARDOPSampleSink(intSample);
|
|
}
|
|
|
|
// Done all samples for this symbol
|
|
// now next symbol of byte
|
|
|
|
}
|
|
intDataPtr++;
|
|
}
|
|
|
|
if (Type == PktFrameHeader)
|
|
{
|
|
// just sent packet header. Send rest in current mode
|
|
|
|
Type = 0; // Prevent reentry
|
|
|
|
strcpy(strMod, &pktMod[pktMode][0]);
|
|
intDataBytesPerCar = pktDataLen + pktRSLen + 3;
|
|
intDataPtr = 11; // Over Header
|
|
intNumCar = pktCarriers[pktMode];
|
|
|
|
switch (intNumCar)
|
|
{
|
|
case 1:
|
|
intCarStartIndex = 4;
|
|
// dblCarScalingFactor = 1.0f; // Starting at 1500 Hz (scaling factors determined emperically to minimize crest factor) TODO: needs verification
|
|
dblCarScalingFactor = 1.2f; // Starting at 1500 Hz Selected to give < 13% clipped values yielding a PAPR = 1.6 Constellation Quality >98
|
|
case 2:
|
|
intCarStartIndex = 3;
|
|
// dblCarScalingFactor = 0.53f;
|
|
if (strcmp(strMod, "16QAM") == 0)
|
|
dblCarScalingFactor = 0.67f; // Carriers at 1400 and 1600 Selected to give < 2.5% clipped values yielding a PAPR = 2.17, Constellation Quality >92
|
|
else
|
|
dblCarScalingFactor = 0.65f; // Carriers at 1400 and 1600 Selected to give < 4% clipped values yielding a PAPR = 2.0, Constellation Quality >95
|
|
break;
|
|
case 4:
|
|
intCarStartIndex = 2;
|
|
// dblCarScalingFactor = 0.29f; // Starting at 1200 Hz
|
|
dblCarScalingFactor = 0.4f; // Starting at 1200 Hz Selected to give < 3% clipped values yielding a PAPR = 2.26, Constellation Quality >95
|
|
break;
|
|
case 8:
|
|
intCarStartIndex = 0;
|
|
// dblCarScalingFactor = 0.17f; // Starting at 800 Hz
|
|
if (strcmp(strMod, "16QAM") == 0)
|
|
dblCarScalingFactor = 0.27f; // Starting at 800 Hz Selected to give < 1% clipped values yielding a PAPR = 2.64, Constellation Quality >94
|
|
else
|
|
dblCarScalingFactor = 0.25f; // Starting at 800 Hz Selected to give < 2% clipped values yielding a PAPR = 2.5, Constellation Quality >95
|
|
}
|
|
goto PktLoopBack; // Reenter to send rest of variable length packet frame
|
|
}
|
|
|
|
ARDOPFlush(Chan);
|
|
|
|
if (intSoftClipCnt > 0)
|
|
Debugprintf("Soft Clips %d ", intSoftClipCnt);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resends the last frame
|
|
|
|
void RemodulateLastFrame(int Chan)
|
|
{
|
|
int intNumCar, intBaud, intDataLen, intRSLen;
|
|
UCHAR bytMinQualThresh;
|
|
BOOL blnOdd;
|
|
|
|
char strType[18] = "";
|
|
char strMod[16] = "";
|
|
|
|
if (!FrameInfo(bytEncodedBytes[0], &blnOdd, &intNumCar, strMod, &intBaud, &intDataLen, &intRSLen, &bytMinQualThresh, strType))
|
|
return;
|
|
|
|
if (strcmp(strMod, "4FSK") == 0)
|
|
{
|
|
Mod4FSKDataAndPlay(bytEncodedBytes, EncLen, intCalcLeader, Chan); // Modulate Data frame
|
|
return;
|
|
}
|
|
|
|
if (strcmp(strMod, "OFDM") == 0)
|
|
{
|
|
int save = OFDMMode;
|
|
OFDMMode = LastSentOFDMMode;
|
|
|
|
ModOFDMDataAndPlay(bytEncodedBytes, EncLen, intCalcLeader, Chan); // Modulate Data frame
|
|
|
|
OFDMMode = save;
|
|
|
|
return;
|
|
}
|
|
|
|
ModPSKDataAndPlay(bytEncodedBytes, EncLen, intCalcLeader, Chan); // Modulate Data frame
|
|
}
|
|
|
|
// Filter State Variables
|
|
|
|
static float dblR = (float)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[34] = {0.0f}; // the coefficients
|
|
float dblZin = 0, dblZin_1 = 0, dblZin_2 = 0, dblZComb= 0; // Used in the comb generator
|
|
|
|
// The resonators
|
|
|
|
float dblZout_0[34] = {0.0f}; // resonator outputs
|
|
float dblZout_1[34] = {0.0f}; // resonator outputs delayed one sample
|
|
float dblZout_2[34] = {0.0f}; // resonator outputs delayed two samples
|
|
|
|
int fWidth; // Filter BandWidth
|
|
int SampleNo;
|
|
int outCount = 0;
|
|
int first, last; // Filter slots
|
|
int centreSlot;
|
|
|
|
float largest = 0;
|
|
float smallest = 0;
|
|
|
|
short Last120[256]; // Now need 240 for 200 Hz filter
|
|
|
|
int Last120Get = 0;
|
|
int Last120Put = 120;
|
|
|
|
extern int Number; // Number waiting to be sent
|
|
|
|
UCHAR bytPendingSessionID;
|
|
UCHAR bytSessionID = 0x3f;
|
|
BOOL blnARQConnected;
|
|
|
|
extern unsigned short buffer[2][1200];
|
|
|
|
unsigned short * DMABuffer;
|
|
|
|
unsigned short * SendtoCard(unsigned short * buf, int n);
|
|
unsigned short * SoundInit();
|
|
|
|
// initFilter is called to set up each packet. It selects filter width
|
|
|
|
void initFilter(int Width, int Centre, int Chan)
|
|
{
|
|
int i, j;
|
|
fWidth = Width;
|
|
centreSlot = Centre / 100;
|
|
largest = smallest = 0;
|
|
SampleNo = 0;
|
|
Number = 0;
|
|
outCount = 0;
|
|
memset(Last120, 0, 256);
|
|
|
|
DMABuffer = &ARDOPTXBuffer[Chan][0];
|
|
|
|
// KeyPTT(TRUE);
|
|
SoundIsPlaying = TRUE;
|
|
// StopCapture();
|
|
|
|
Last120Get = 0;
|
|
Last120Put = 120;
|
|
|
|
dblRn = powf(dblR, intN);
|
|
dblR2 = powf(dblR, 2);
|
|
|
|
dblZin_2 = dblZin_1 = 0;
|
|
|
|
switch (fWidth)
|
|
{
|
|
case 200:
|
|
|
|
// Used for PSK 200 Hz modulation XMIT filter
|
|
// implements 5 50 Hz wide sections centered on 1500 Hz (~200 Hz wide @ - 30dB centered on 1500 Hz)
|
|
|
|
SendingHeader200 = TRUE;
|
|
intN = 240;
|
|
Last120Put = 240;
|
|
centreSlot = Centre / 50;
|
|
first = centreSlot - 3;
|
|
last = centreSlot + 3; // 7 filter sections
|
|
break;
|
|
|
|
case 500:
|
|
|
|
// implements 7 100 Hz wide sections centered on 1500 Hz (~500 Hz wide @ - 30dB centered on 1500 Hz)
|
|
|
|
intN = 120;
|
|
first = centreSlot - 3;
|
|
last = centreSlot + 3; // 7 filter sections
|
|
break;
|
|
|
|
case 2500:
|
|
|
|
// implements 26 100 Hz wide sections centered on 1500 Hz (~2000 Hz wide @ - 30dB centered on 1500 Hz)
|
|
|
|
intN = 120;
|
|
first = centreSlot - 13;
|
|
last = centreSlot + 13; // 27 filter sections
|
|
break;
|
|
|
|
default:
|
|
|
|
Debugprintf("Invalid Filter Width %d", fWidth);
|
|
}
|
|
|
|
|
|
for (j = first; j <= last; j++)
|
|
{
|
|
dblZout_0[j] = 0;
|
|
dblZout_1[j] = 0;
|
|
dblZout_2[j] = 0;
|
|
}
|
|
|
|
// Initialise the coefficients
|
|
|
|
// if (dblCoef[last] == 0.0)
|
|
{
|
|
for (i = first; i <= last; i++)
|
|
{
|
|
double x = 2 * M_PI * i / intN;
|
|
x = cosf(1);
|
|
|
|
dblCoef[i] = 2.0 * dblR * cosf(2 * M_PI * i / intN); // For Frequency = bin i
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void ARDOPSampleSink(short Sample)
|
|
{
|
|
// Filter and send to sound interface
|
|
|
|
// This version is passed samples one at a time, as we don't have
|
|
// enough RAM in embedded systems to hold a full audio frame
|
|
|
|
int intFilLen = intN / 2;
|
|
int j;
|
|
float intFilteredSample = 0; // Filtered sample
|
|
|
|
// We save the previous intN samples
|
|
// The samples are held in a cyclic buffer
|
|
|
|
if (SampleNo < intN)
|
|
dblZin = Sample;
|
|
else
|
|
dblZin = Sample - dblRn * Last120[Last120Get];
|
|
|
|
if (++Last120Get == (intN + 1))
|
|
Last120Get = 0;
|
|
|
|
// Compute the Comb
|
|
|
|
dblZComb = dblZin - dblZin_2 * dblR2;
|
|
dblZin_2 = dblZin_1;
|
|
dblZin_1 = dblZin;
|
|
|
|
// Now the resonators
|
|
|
|
for (j = first; j <= last; j++)
|
|
{
|
|
dblZout_0[j] = dblZComb + dblCoef[j] * dblZout_1[j] - dblR2 * dblZout_2[j];
|
|
|
|
if (dblZout_0[j] != dblZout_0[j])
|
|
j = j;
|
|
|
|
dblZout_2[j] = dblZout_1[j];
|
|
dblZout_1[j] = dblZout_0[j];
|
|
|
|
switch (fWidth)
|
|
{
|
|
case 200:
|
|
|
|
// scale each by transition coeff and + (Even) or - (Odd)
|
|
|
|
if (SampleNo >= intFilLen)
|
|
{
|
|
if (j == first || j == last)
|
|
{
|
|
if (SendingHeader200)
|
|
intFilteredSample -= dblZout_0[j]; // This provides no attenuation to the Frame Type tones at 1350 and 1650
|
|
else
|
|
intFilteredSample -= 0.1 * dblZout_0[j]; // This smaller value required to filter down to 200 Hz bandwidth
|
|
}
|
|
else if ((j & 1) == 0)
|
|
intFilteredSample += (int)dblZout_0[j];
|
|
else
|
|
intFilteredSample -= (int)dblZout_0[j];
|
|
}
|
|
|
|
break;
|
|
|
|
case 500:
|
|
|
|
// scale each by transition coeff and + (Even) or - (Odd)
|
|
// Resonators 12 and 18 scaled to get best shape and side lobe supression to - 45 dB while keeping BW at 500 Hz @ -26 dB
|
|
// practical range of scaling .05 to .25
|
|
// Scaling also accomodates for the filter "gain" of approx 60.
|
|
|
|
if (SampleNo >= intFilLen)
|
|
{
|
|
if (j == first || j == last)
|
|
intFilteredSample += 0.389f * dblZout_0[j];
|
|
else if ((j & 1) == 0)
|
|
intFilteredSample += (int)dblZout_0[j];
|
|
else
|
|
intFilteredSample -= (int)dblZout_0[j];
|
|
}
|
|
|
|
break;
|
|
|
|
case 2500:
|
|
|
|
// scale each by transition coeff and + (Even) or - (Odd)
|
|
// Resonators 2 and 28 scaled to get best shape and side lobe supression to - 45 dB while keeping BW at 500 Hz @ -26 dB
|
|
// practical range of scaling .05 to .25
|
|
// Scaling also accomodates for the filter "gain" of approx 60.
|
|
|
|
if (SampleNo >= intFilLen)
|
|
{
|
|
if (j == first || j == last)
|
|
intFilteredSample += 0.3891f * dblZout_0[j];
|
|
else if ((j & 1) == 0) // Even
|
|
intFilteredSample += (int)dblZout_0[j];
|
|
else
|
|
intFilteredSample -= (int)dblZout_0[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SampleNo >= intFilLen)
|
|
{
|
|
intFilteredSample = intFilteredSample * 0.00833333333f; // rescales for gain of filter
|
|
largest = max(largest, intFilteredSample);
|
|
smallest = min(smallest, intFilteredSample);
|
|
|
|
if (intFilteredSample > 32700) // Hard clip above 32700
|
|
intFilteredSample = 32700;
|
|
else if (intFilteredSample < -32700)
|
|
intFilteredSample = -32700;
|
|
|
|
#ifdef TEENSY
|
|
int work = (short)(intFilteredSample);
|
|
DMABuffer[Number++] = (work + 32768) >> 4; // 12 bit left justify
|
|
#else
|
|
DMABuffer[Number++] = (short)intFilteredSample;
|
|
#endif
|
|
if (Number == ARDOPBufferSize)
|
|
{
|
|
// send this buffer to sound interface (shouldn't happen)
|
|
|
|
DMABuffer = SendtoCard(DMABuffer, SendSize);
|
|
Number = 0;
|
|
}
|
|
}
|
|
|
|
Last120[Last120Put++] = Sample;
|
|
|
|
if (Last120Put == (intN + 1))
|
|
Last120Put = 0;
|
|
|
|
SampleNo++;
|
|
}
|
|
|
|
|
|
|
|
extern int dttTimeoutTrip;
|
|
|
|
extern UCHAR bytSessionID;
|
|
|
|
|
|
// Subroutine to make a CW ID Wave File
|
|
|
|
void sendCWID(char * strID, BOOL CWOnOff, int Chan)
|
|
{
|
|
// This generates a phase synchronous FSK MORSE keying of strID
|
|
// FSK used to maintain VOX on some sound cards
|
|
// Sent at 90% of max ampllitude
|
|
|
|
char strAlphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/";
|
|
|
|
//Look up table for strAlphabet...each bit represents one dot time, 3 adjacent dots = 1 dash
|
|
// one dot spacing between dots or dashes
|
|
|
|
int intCW[] = {0x17, 0x1D5, 0x75D, 0x75, 0x1, 0x15D,
|
|
0x1DD, 0x55, 0x5, 0x1777, 0x1D7, 0x175,
|
|
0x77, 0x1D, 0x777, 0x5DD, 0x1DD7, 0x5D,
|
|
0x15, 0x7, 0x57, 0x157, 0x177, 0x757,
|
|
0x1D77, 0x775, 0x77777, 0x17777, 0x5777, 0x1577,
|
|
0x557, 0x155, 0x755, 0x1DD5, 0x7775, 0x1DDDD, 0x1D57, 0x1D57};
|
|
|
|
|
|
float dblHiPhaseInc = 2 * M_PI * 1509.375f / 12000; // 1609.375 Hz High tone
|
|
float dblLoPhaseInc = 2 * M_PI * 1390.625f / 12000; // 1390.625 low tone
|
|
float dblHiPhase = 0;
|
|
float dblLoPhase = 0;
|
|
int intDotSampCnt = 768; // about 12 WPM or so (should be a multiple of 256
|
|
short intDot[768];
|
|
short intSpace[768];
|
|
int i, j, k;
|
|
int intAmp = 26000; // Selected to have some margin in calculations with 16 bit values (< 32767) this must apply to all filters as well.
|
|
char * index;
|
|
int intMask;
|
|
int idoffset;
|
|
int Filter = 1500;
|
|
|
|
if (CWIDMark[0])
|
|
{
|
|
// Want nonstandard tones
|
|
|
|
float Mark = atof(CWIDMark);
|
|
float Space = Mark - 200;
|
|
|
|
dblHiPhaseInc = 2 * M_PI * Mark / 12000; // 1609.375 Hz High tone
|
|
dblLoPhaseInc = 2 * M_PI * Space / 12000; // 1390.625 low tone
|
|
|
|
if (CWOnOff)
|
|
Filter = Mark;
|
|
else
|
|
Filter = (Mark + Space) / 2;
|
|
}
|
|
|
|
strlop(strID, '-'); // Remove any SSID
|
|
|
|
// Generate the dot samples (high tone) and space samples (low tone)
|
|
|
|
for (i = 0; i < intDotSampCnt; i++)
|
|
{
|
|
if (CWOnOff)
|
|
intSpace[i] = 0;
|
|
else
|
|
intSpace[i] = sin(dblLoPhase) * 0.9 * intAmp;
|
|
|
|
intDot[i] = sin(dblHiPhase) * 0.9 * intAmp;
|
|
|
|
dblHiPhase += dblHiPhaseInc;
|
|
if (dblHiPhase > 2 * M_PI)
|
|
dblHiPhase -= 2 * M_PI;
|
|
dblLoPhase += dblLoPhaseInc;
|
|
if (dblLoPhase > 2 * M_PI)
|
|
dblLoPhase -= 2 * M_PI;
|
|
}
|
|
|
|
if (CWOnOff)
|
|
initFilter(500, Filter, Chan);
|
|
else
|
|
initFilter(200, Filter, Chan);
|
|
|
|
|
|
// if sending 1500 cal tone send mark tone for 10 secs
|
|
|
|
if (strcmp(strID, "1500TONE") == 0)
|
|
{
|
|
float m_amplitude = 30000.0f;
|
|
float m_frequency = 1500.0f;
|
|
float m_phase = 0.0;
|
|
float m_time = 0.0;
|
|
float m_deltaTime = 1.0f / 12000;
|
|
|
|
float x;
|
|
// generate sin wave in mono
|
|
for (int sample = 0; sample < 120000; ++sample)
|
|
{
|
|
x = m_amplitude * sin(2 * M_PI * m_frequency * m_time + m_phase);
|
|
ARDOPSampleSink(x);
|
|
m_time += m_deltaTime;
|
|
}
|
|
|
|
|
|
ARDOPTXPtr[Chan] = 0;
|
|
ARDOPTXLen[Chan] = Number;
|
|
Number = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//Generate leader for VOX 6 dots long
|
|
|
|
for (k = 6; k >0; k--)
|
|
for (i = 0; i < intDotSampCnt; i++)
|
|
ARDOPSampleSink(intSpace[i]);
|
|
|
|
for (j = 0; j < strlen(strID); j++)
|
|
{
|
|
index = strchr(strAlphabet, strID[j]);
|
|
if (index)
|
|
idoffset = index - &strAlphabet[0];
|
|
else
|
|
idoffset = 0;
|
|
|
|
intMask = 0x40000000;
|
|
|
|
if (index == NULL)
|
|
{
|
|
// process this as a space adding 6 dots worth of space to the wave file
|
|
|
|
for (k = 6; k >0; k--)
|
|
for (i = 0; i < intDotSampCnt; i++)
|
|
ARDOPSampleSink(intSpace[i]);
|
|
}
|
|
else
|
|
{
|
|
while (intMask > 0) // search for the first non 0 bit
|
|
if (intMask & intCW[idoffset])
|
|
break; // intMask is pointing to the first non 0 entry
|
|
else
|
|
intMask >>= 1; // Right shift mask
|
|
|
|
while (intMask > 0) // search for the first non 0 bit
|
|
{
|
|
if (intMask & intCW[idoffset])
|
|
for (i = 0; i < intDotSampCnt; i++)
|
|
ARDOPSampleSink(intDot[i]);
|
|
else
|
|
for (i = 0; i < intDotSampCnt; i++)
|
|
ARDOPSampleSink(intSpace[i]);
|
|
|
|
intMask >>= 1; // Right shift mask
|
|
}
|
|
}
|
|
// add 2 dot spaces for inter letter spacing
|
|
for (k = 4; k >0; k--)
|
|
for (i = 0; i < intDotSampCnt; i++)
|
|
ARDOPSampleSink(intSpace[i]);
|
|
}
|
|
|
|
//add 3 spaces for the end tail
|
|
|
|
// for (k = 6; k >0; k--)
|
|
// for (i = 0; i < intDotSampCnt; i++)
|
|
// ARDOPSampleSink(intSpace[i]);
|
|
|
|
ARDOPTXPtr[Chan] = 0;
|
|
ARDOPTXLen[Chan] = Number;
|
|
Number = 0;
|
|
}
|
|
|
|
|