kopia lustrzana https://github.com/jamescoxon/dl-fldigi
935 wiersze
19 KiB
C++
935 wiersze
19 KiB
C++
// ----------------------------------------------------------------------------
|
|
// rtty.cxx -- RTTY modem
|
|
//
|
|
// Copyright (C) 2006-2010
|
|
// Dave Freese, W1HKJ
|
|
//
|
|
// This file is part of fldigi. Adapted from code contained in gmfsk source code
|
|
// distribution.
|
|
// gmfsk Copyright (C) 2001, 2002, 2003
|
|
// Tomi Manninen (oh2bns@sral.fi)
|
|
//
|
|
// Fldigi is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Fldigi is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with fldigi. If not, see <http://www.gnu.org/licenses/>.
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#include <config.h>
|
|
#include <iostream>
|
|
using namespace std;
|
|
|
|
#include "view_rtty.h"
|
|
#include "fl_digi.h"
|
|
#include "digiscope.h"
|
|
#include "misc.h"
|
|
#include "waterfall.h"
|
|
#include "confdialog.h"
|
|
#include "configuration.h"
|
|
#include "status.h"
|
|
#include "digiscope.h"
|
|
#include "trx.h"
|
|
|
|
#include "dl_fldigi/hbtint.h"
|
|
|
|
view_rtty *rttyviewer = (view_rtty *)0;
|
|
|
|
//=====================================================================
|
|
// Baudot support
|
|
//=====================================================================
|
|
|
|
static unsigned char letters[32] = {
|
|
'\0', 'E', '\n', 'A', ' ', 'S', 'I', 'U',
|
|
'\r', 'D', 'R', 'J', 'N', 'F', 'C', 'K',
|
|
'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q',
|
|
'O', 'B', 'G', '·', 'M', 'X', 'V', '·'
|
|
};
|
|
|
|
#if 0
|
|
/*
|
|
* ITA-2 version of the figures case.
|
|
*/
|
|
static unsigned char figures[32] = {
|
|
'\0', '3', '\n', '-', ' ', '\'', '8', '7',
|
|
'\r', '·', '4', '\a', ',', '·', ':', '(',
|
|
'5', '+', ')', '2', '·', '6', '0', '1',
|
|
'9', '?', '·', '·', '.', '/', '=', '·'
|
|
};
|
|
#endif
|
|
#if 1
|
|
/*
|
|
* U.S. version of the figures case.
|
|
*/
|
|
static unsigned char figures[32] = {
|
|
'\0', '3', '\n', '-', ' ', '\a', '8', '7',
|
|
'\r', '$', '4', '\'', ',', '!', ':', '(',
|
|
'5', '"', ')', '2', '#', '6', '0', '1',
|
|
'9', '?', '&', '·', '.', '/', ';', '·'
|
|
};
|
|
#endif
|
|
#if 0
|
|
/*
|
|
* A mix of the two. This is what seems to be what people actually use.
|
|
*/
|
|
static unsigned char figures[32] = {
|
|
'\0', '3', '\n', '-', ' ', '\'', '8', '7',
|
|
'\r', '$', '4', '\a', ',', '!', ':', '(',
|
|
'5', '+', ')', '2', '#', '6', '0', '1',
|
|
'9', '?', '&', '·', '.', '/', '=', '·'
|
|
};
|
|
#endif
|
|
|
|
int dspcnt = 0;
|
|
|
|
|
|
static char msg1[20];
|
|
|
|
/* Terminating 0 at the end of the list for dl_fldigi/flights.cxx */
|
|
const double rtty::SHIFT[] = {23, 85, 160, 170, 182, 200, 240, 350, 425, 600, 850, 0};
|
|
const double rtty::BAUD[] = {45, 45.45, 50, 56, 75, 100, 110, 150, 200, 300, 600, 1200, 0};
|
|
const int rtty::BITS[] = {5, 7, 8};
|
|
|
|
void rtty::tx_init(SoundBase *sc)
|
|
{
|
|
// start each new transmission 20 bit lengths MARK tone
|
|
scard = sc;
|
|
phaseacc = 0;
|
|
preamble = 20;
|
|
videoText();
|
|
}
|
|
|
|
void rtty::rx_init()
|
|
{
|
|
rxstate = RTTY_RX_STATE_IDLE;
|
|
rxmode = LETTERS;
|
|
phaseacc = 0;
|
|
FSKphaseacc = 0;
|
|
for (int i = 0; i < RTTYMaxSymLen; i++ ) {
|
|
bbfilter[i] = 0.0;
|
|
}
|
|
bitfilt->reset();
|
|
poserr = negerr = 0.0;
|
|
lost = 0;
|
|
}
|
|
|
|
void rtty::init()
|
|
{
|
|
bool wfrev = wf->Reverse();
|
|
bool wfsb = wf->USB();
|
|
reverse = wfrev ^ !wfsb;
|
|
stopflag = false;
|
|
|
|
if (progdefaults.StartAtSweetSpot)
|
|
set_freq(progdefaults.RTTYsweetspot);
|
|
else if (progStatus.carrier != 0) {
|
|
set_freq(progStatus.carrier);
|
|
#if !BENCHMARK_MODE
|
|
progStatus.carrier = 0;
|
|
#endif
|
|
} else
|
|
set_freq(wf->Carrier());
|
|
|
|
rx_init();
|
|
put_MODEstatus(mode);
|
|
if ((rtty_baud - (int)rtty_baud) == 0)
|
|
snprintf(msg1, sizeof(msg1), "%-3.0f / %-4.0f", rtty_baud, rtty_shift);
|
|
else
|
|
snprintf(msg1, sizeof(msg1), "%-4.2f / %-4.0f", rtty_baud, rtty_shift);
|
|
put_Status1(msg1);
|
|
if (progdefaults.PreferXhairScope)
|
|
set_scope_mode(Digiscope::XHAIRS);
|
|
else
|
|
set_scope_mode(Digiscope::RTTY);
|
|
}
|
|
|
|
rtty::~rtty()
|
|
{
|
|
if (hilbert) delete hilbert;
|
|
if (bitfilt) delete bitfilt;
|
|
if (bpfilt) delete bpfilt;
|
|
if (pipe) delete [] pipe;
|
|
if (dsppipe) delete [] dsppipe;
|
|
}
|
|
|
|
void rtty::restart()
|
|
{
|
|
double stl;
|
|
|
|
rtty_shift = shift = (progdefaults.rtty_shift >= 0 ?
|
|
SHIFT[progdefaults.rtty_shift] : progdefaults.rtty_custom_shift);
|
|
rtty_baud = BAUD[progdefaults.rtty_baud];
|
|
nbits = rtty_bits = BITS[progdefaults.rtty_bits];
|
|
if (rtty_bits == 5)
|
|
rtty_parity = RTTY_PARITY_NONE;
|
|
else
|
|
switch (progdefaults.rtty_parity) {
|
|
case 0 : rtty_parity = RTTY_PARITY_NONE; break;
|
|
case 1 : rtty_parity = RTTY_PARITY_EVEN; break;
|
|
case 2 : rtty_parity = RTTY_PARITY_ODD; break;
|
|
case 3 : rtty_parity = RTTY_PARITY_ZERO; break;
|
|
case 4 : rtty_parity = RTTY_PARITY_ONE; break;
|
|
default : rtty_parity = RTTY_PARITY_NONE; break;
|
|
}
|
|
rtty_stop = progdefaults.rtty_stop;
|
|
|
|
txmode = LETTERS;
|
|
rxmode = LETTERS;
|
|
symbollen = (int) (samplerate / rtty_baud + 0.5);
|
|
set_bandwidth(shift);
|
|
|
|
if (progdefaults.RTTY_BW < rtty_baud)
|
|
progdefaults.RTTY_BW = rtty_baud;
|
|
rtty_BW = progdefaults.RTTY_BW;
|
|
sldrRTTYbandwidth->value(rtty_BW);
|
|
|
|
wf->redraw_marker();
|
|
|
|
bp_filt_lo = (shift/2.0 - rtty_BW/2.0) / samplerate;
|
|
if (bp_filt_lo < 0) bp_filt_lo = 0;
|
|
bp_filt_hi = (shift/2.0 + rtty_BW/2.0) / samplerate;
|
|
|
|
if (bpfilt)
|
|
bpfilt->create_filter(bp_filt_lo, bp_filt_hi);
|
|
else
|
|
bpfilt = new fftfilt(bp_filt_lo, bp_filt_hi, 1024);
|
|
|
|
bflen = symbollen/3;
|
|
if (bitfilt)
|
|
bitfilt->setLength(bflen);
|
|
else
|
|
bitfilt = new Cmovavg(bflen);
|
|
|
|
// stop length = 1, 1.5 or 2 bits
|
|
rtty_stop = progdefaults.rtty_stop;
|
|
if (rtty_stop == 0) stl = 1.0;
|
|
else if (rtty_stop == 1) stl = 1.5;
|
|
else stl = 2.0;
|
|
stoplen = (int) (stl * samplerate / rtty_baud + 0.5);
|
|
freqerr = 0.0;
|
|
filterptr = 0;
|
|
pipeptr = 0;
|
|
poscnt = negcnt = 0;
|
|
posfreq = negfreq = 0.0;
|
|
|
|
/* The length of a byte (including start, stop and parity bits */
|
|
bytelen = (1 + nbits + stl + (rtty_parity == RTTY_PARITY_NONE ? 0 : 1)) * symbollen;
|
|
|
|
metric = 0.0;
|
|
|
|
if ((rtty_baud - (int)rtty_baud) == 0)
|
|
snprintf(msg1, sizeof(msg1), "%-3.0f / %-4.0f", rtty_baud, rtty_shift);
|
|
else
|
|
snprintf(msg1, sizeof(msg1), "%-4.2f / %-4.0f", rtty_baud, rtty_shift);
|
|
put_Status1(msg1);
|
|
put_MODEstatus(mode);
|
|
for (int i = 0; i < 1024; i++) QI[i].re = QI[i].im = 0.0;
|
|
sigpwr = 0.0;
|
|
noisepwr = 0.0;
|
|
freqerrlo = freqerrhi = 0.0;
|
|
sigsearch = 0;
|
|
dspcnt = 2*(nbits + 2);
|
|
|
|
clear_zdata = true;
|
|
|
|
rttyviewer->restart();
|
|
|
|
}
|
|
|
|
rtty::rtty(trx_mode tty_mode)
|
|
{
|
|
cap |= CAP_AFC | CAP_REV;
|
|
|
|
mode = tty_mode;
|
|
|
|
samplerate = RTTY_SampleRate;
|
|
|
|
bpfilt = (fftfilt *)0;
|
|
|
|
bitfilt = (Cmovavg *)0;
|
|
|
|
hilbert = new C_FIR_filter();
|
|
hilbert->init_hilbert(37, 1);
|
|
|
|
pipe = new double[MAXPIPE];
|
|
dsppipe = new double [MAXPIPE];
|
|
|
|
samples = new complex[8];
|
|
|
|
::rttyviewer = new view_rtty(mode);
|
|
|
|
restart();
|
|
}
|
|
|
|
void rtty::update_syncscope()
|
|
{
|
|
int j;
|
|
for (int i = 0; i < symbollen; i++) {
|
|
j = pipeptr - i;
|
|
if (j < 0) j += symbollen;
|
|
dsppipe[i] = pipe[j];
|
|
}
|
|
set_scope(dsppipe, symbollen, false);
|
|
}
|
|
|
|
void rtty::clear_syncscope()
|
|
{
|
|
set_scope(0, 0, false);
|
|
}
|
|
|
|
complex rtty::mixer(complex in)
|
|
{
|
|
complex z;
|
|
z.re = cos(phaseacc);
|
|
z.im = sin(phaseacc);
|
|
z = z * in;
|
|
|
|
phaseacc -= TWOPI * frequency / samplerate;
|
|
if (phaseacc > M_PI)
|
|
phaseacc -= TWOPI;
|
|
else if (phaseacc < M_PI)
|
|
phaseacc += TWOPI;
|
|
|
|
return z;
|
|
}
|
|
|
|
unsigned char rtty::bitreverse(unsigned char in, int n)
|
|
{
|
|
unsigned char out = 0;
|
|
|
|
for (int i = 0; i < n; i++)
|
|
out = (out << 1) | ((in >> i) & 1);
|
|
|
|
return out;
|
|
}
|
|
|
|
static int rparity(int c)
|
|
{
|
|
int w = c;
|
|
int p = 0;
|
|
while (w) {
|
|
p += (w & 1);
|
|
w >>= 1;
|
|
}
|
|
return p & 1;
|
|
}
|
|
|
|
int rtty::rttyparity(unsigned int c)
|
|
{
|
|
c &= (1 << nbits) - 1;
|
|
|
|
switch (rtty_parity) {
|
|
default:
|
|
case RTTY_PARITY_NONE:
|
|
return 0;
|
|
|
|
case RTTY_PARITY_ODD:
|
|
return rparity(c);
|
|
|
|
case RTTY_PARITY_EVEN:
|
|
return !rparity(c);
|
|
|
|
case RTTY_PARITY_ZERO:
|
|
return 0;
|
|
|
|
case RTTY_PARITY_ONE:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int rtty::decode_char()
|
|
{
|
|
unsigned int parbit, par, data;
|
|
|
|
parbit = (rxdata >> nbits) & 1;
|
|
par = rttyparity(rxdata);
|
|
|
|
if (rtty_parity != RTTY_PARITY_NONE && parbit != par)
|
|
return 0;
|
|
|
|
data = rxdata & ((1 << nbits) - 1);
|
|
|
|
if (nbits == 5)
|
|
return baudot_dec(data);
|
|
|
|
return data;
|
|
}
|
|
|
|
bool rtty::rx(bool bit)
|
|
{
|
|
bool flag = false;
|
|
unsigned char c;
|
|
int lb;
|
|
|
|
lost++;
|
|
|
|
switch (rxstate) {
|
|
case RTTY_RX_STATE_IDLE:
|
|
if (!bit) {
|
|
rxstate = RTTY_RX_STATE_START;
|
|
counter = symbollen / 2;
|
|
}
|
|
break;
|
|
|
|
case RTTY_RX_STATE_START:
|
|
if (--counter == 0) {
|
|
if (!bit) {
|
|
rxstate = RTTY_RX_STATE_DATA;
|
|
counter = symbollen;
|
|
bitcntr = 0;
|
|
rxdata = 0;
|
|
} else {
|
|
rxstate = RTTY_RX_STATE_IDLE;
|
|
}
|
|
} else
|
|
if (bit) rxstate = RTTY_RX_STATE_IDLE;
|
|
break;
|
|
|
|
case RTTY_RX_STATE_DATA:
|
|
if (--counter == 0) {
|
|
rxdata |= bit << bitcntr++;
|
|
counter = symbollen;
|
|
}
|
|
|
|
if (bitcntr == nbits) {
|
|
if (rtty_parity == RTTY_PARITY_NONE) {
|
|
rxstate = RTTY_RX_STATE_STOP;
|
|
}
|
|
else {
|
|
rxstate = RTTY_RX_STATE_PARITY;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case RTTY_RX_STATE_PARITY:
|
|
if (--counter == 0) {
|
|
rxstate = RTTY_RX_STATE_STOP;
|
|
rxdata |= bit << bitcntr++;
|
|
counter = symbollen;
|
|
}
|
|
break;
|
|
|
|
case RTTY_RX_STATE_STOP:
|
|
if (--counter == 0) {
|
|
if (bit) {
|
|
if ((metric >= progStatus.sldrSquelchValue && progStatus.sqlonoff)|| !progStatus.sqlonoff) {
|
|
c = decode_char();
|
|
|
|
/* lb = estimated bytes lost */
|
|
lb = (lost - bytelen / 2) / bytelen;
|
|
|
|
/* HOOKS */
|
|
put_rx_ssdv(c, lb);
|
|
|
|
if (lb != 0)
|
|
dl_fldigi::hbtint::extrmgr->skipped(lb);
|
|
|
|
if (nbits == 5)
|
|
dl_fldigi::hbtint::extrmgr->push(c, habitat::PUSH_BAUDOT_HACK);
|
|
else
|
|
dl_fldigi::hbtint::extrmgr->push(c);
|
|
|
|
if ( c != 0 )
|
|
put_rx_char(progdefaults.rx_lowercase ? tolower(c) : c, FTextBase::RECV, true);
|
|
}
|
|
flag = true;
|
|
lost = 0;
|
|
}
|
|
rxstate = RTTY_RX_STATE_STOP2;
|
|
counter = symbollen / 2;
|
|
}
|
|
break;
|
|
|
|
case RTTY_RX_STATE_STOP2:
|
|
if (--counter == 0) {
|
|
rxstate = RTTY_RX_STATE_IDLE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return flag;
|
|
}
|
|
|
|
char snrmsg[80];
|
|
void rtty::Metric()
|
|
{
|
|
double delta = rtty_baud/2.0;
|
|
double np =
|
|
wf->powerDensity(frequency - shift * 1.5, delta) +
|
|
wf->powerDensity(frequency + shift * 1.5, delta) + 1e-10;
|
|
double sp =
|
|
wf->powerDensity(frequency - shift/2, delta) +
|
|
wf->powerDensity(frequency + shift/2, delta) + 1e-10;
|
|
double snr = 0;
|
|
|
|
sigpwr = decayavg( sigpwr, sp, sp - sigpwr > 0 ? 2 : 8);
|
|
noisepwr = decayavg( noisepwr, np, 32 );
|
|
snr = 10*log10(sigpwr / ( noisepwr * (1500 / delta))); // 3000 Hz noise bw
|
|
|
|
snprintf(snrmsg, sizeof(snrmsg), "s/n %3.0f dB", snr);
|
|
put_Status2(snrmsg);
|
|
metric = CLAMP(sigpwr/noisepwr, 0.0, 100.0);
|
|
display_metric(metric);
|
|
}
|
|
|
|
void rtty::searchDown()
|
|
{
|
|
double srchfreq = frequency - shift -100;
|
|
double minfreq = shift * 2 + 100;
|
|
double spwrlo, spwrhi, npwr;
|
|
while (srchfreq > minfreq) {
|
|
spwrlo = wf->powerDensity(srchfreq - shift/2, 2*rtty_baud);
|
|
spwrhi = wf->powerDensity(srchfreq + shift/2, 2*rtty_baud);
|
|
npwr = wf->powerDensity(srchfreq + shift, 2*rtty_baud) + 1e-10;
|
|
if ((spwrlo / npwr > 10.0) && (spwrhi / npwr > 10.0)) {
|
|
frequency = srchfreq;
|
|
set_freq(frequency);
|
|
sigsearch = SIGSEARCH;
|
|
break;
|
|
}
|
|
srchfreq -= 5.0;
|
|
}
|
|
}
|
|
|
|
void rtty::searchUp()
|
|
{
|
|
double srchfreq = frequency + shift +100;
|
|
double maxfreq = IMAGE_WIDTH - shift * 2 - 100;
|
|
double spwrhi, spwrlo, npwr;
|
|
while (srchfreq < maxfreq) {
|
|
spwrlo = wf->powerDensity(srchfreq - shift/2, 2*rtty_baud);
|
|
spwrhi = wf->powerDensity(srchfreq + shift/2, 2*rtty_baud);
|
|
npwr = wf->powerDensity(srchfreq - shift, 2*rtty_baud) + 1e-10;
|
|
if ((spwrlo / npwr > 10.0) && (spwrhi / npwr > 10.0)) {
|
|
frequency = srchfreq;
|
|
set_freq(frequency);
|
|
sigsearch = SIGSEARCH;
|
|
break;
|
|
}
|
|
srchfreq += 5.0;
|
|
}
|
|
}
|
|
|
|
int rtty::rx_process(const double *buf, int len)
|
|
{
|
|
complex z, *zp;
|
|
double f = 0.0;
|
|
double fin;
|
|
static bool bit = true;
|
|
int n = 0;
|
|
double deadzone = shift/4;
|
|
double rotate;
|
|
double ferr = 0;
|
|
|
|
if (progdefaults.RTTY_BW != rtty_BW) {
|
|
rtty_BW = progdefaults.RTTY_BW;
|
|
bp_filt_lo = (shift/2.0 - rtty_BW/2.0) / samplerate;
|
|
if (bp_filt_lo < 0) bp_filt_lo = 0;
|
|
bp_filt_hi = (shift/2.0 + rtty_BW/2.0) / samplerate;
|
|
bpfilt->create_filter(bp_filt_lo, bp_filt_hi);
|
|
wf->redraw_marker();
|
|
}
|
|
|
|
if (rttyviewer && !bHistory) rttyviewer->rx_process(buf, len);
|
|
|
|
Metric();
|
|
|
|
while (len-- > 0) {
|
|
|
|
// create analytic signal from sound card input samples
|
|
|
|
z.re = z.im = *buf++;
|
|
hilbert->run(z, z);
|
|
|
|
// mix it with the audio carrier frequency to create a baseband signal
|
|
|
|
z = mixer(z);
|
|
|
|
// bandpass filter using Windowed Sinc - Overlap-Add convolution filter
|
|
|
|
n = bpfilt->run(z, &zp);
|
|
|
|
if (n) {
|
|
for (int i = 0; i < n; i++) {
|
|
|
|
// measure phase difference between successive samples to determine
|
|
// the frequency of the baseband signal (+rtty_baud or -rtty_baud)
|
|
// see class complex definiton for operator %
|
|
|
|
fin = (prevsmpl % zp[i]).arg() * samplerate / TWOPI;
|
|
prevsmpl = zp[i];
|
|
|
|
|
|
// track the + and - frequency excursions separately to derive an afc signal
|
|
|
|
rotate = -4.0 * M_PI * freqerr / rtty_shift;
|
|
double xmix = fabs(4.0 * freqerr / rtty_shift);
|
|
if (xmix > 0.5) xmix = 0.5;
|
|
xmix += 0.05;
|
|
if (fin > 0.0) {
|
|
poscnt++;
|
|
posfreq += fin;
|
|
QI[i].re = zp[i].re;
|
|
QI[i].im = xmix * zp[i].im;
|
|
}
|
|
if (fin < 0.0) {
|
|
negcnt++;
|
|
negfreq += fin;
|
|
QI[i].re = xmix * zp[i].im;
|
|
QI[i].im = zp[i].re;
|
|
}
|
|
QI[i] = QI[i] * complex(cos(rotate), sin(rotate));
|
|
avgsig = decayavg(avgsig, 1.25 * zp[i].mag(), 128);//64);
|
|
|
|
if (avgsig > 0)
|
|
QI[i] = QI[i] / avgsig;
|
|
|
|
fin = CLAMP(fin, - rtty_shift, rtty_shift);
|
|
// filter the result with a moving average filter
|
|
f = bitfilt->run(fin);
|
|
// hysterisis dead zone in frequency discriminator bit detector
|
|
if (f > deadzone )
|
|
bit = true;
|
|
if (f < -deadzone)
|
|
bit = false;
|
|
|
|
if (dspcnt && (--dspcnt % (nbits + 2) == 0)) {
|
|
pipe[pipeptr] = f / shift;
|
|
pipeptr = (pipeptr + 1) % symbollen;
|
|
}
|
|
|
|
if ( rx( reverse ? !bit : bit ) ) {
|
|
dspcnt = symbollen * (nbits + 2);
|
|
|
|
if (poscnt && negcnt) {
|
|
poserr = posfreq/poscnt;
|
|
negerr = negfreq/negcnt;
|
|
|
|
// compute the frequency error as the median of + and - relative freq's
|
|
if (sigsearch) sigsearch--;
|
|
|
|
ferr = -(poserr + negerr)/(2*(SIGSEARCH - sigsearch + 1));
|
|
|
|
int fs = progdefaults.rtty_afcspeed;
|
|
int avging;
|
|
if (fs == 0) avging = 8;
|
|
else if (fs == 1) avging = 4;
|
|
else avging = 1;
|
|
freqerr = decayavg(freqerr, ferr, avging);
|
|
|
|
poscnt = 0; posfreq = 0.0;
|
|
negcnt = 0; negfreq = 0.0;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (progStatus.afconoff) {
|
|
if (metric > progStatus.sldrSquelchValue || !progStatus.sqlonoff) // || sigsearch) {
|
|
set_freq(frequency - ferr);
|
|
}
|
|
if (metric < progStatus.sldrSquelchValue && progStatus.sqlonoff) {
|
|
if (clear_zdata) {
|
|
for (int i = 0; i < MAX_ZLEN; i++) QI[i].re = QI[i].im = 0.0;
|
|
set_zdata(QI, MAX_ZLEN);
|
|
clear_zdata = false;
|
|
clear_syncscope();
|
|
}
|
|
} else {
|
|
clear_zdata = true;
|
|
update_syncscope();
|
|
set_zdata(QI, n);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//=====================================================================
|
|
// RTTY transmit
|
|
//=====================================================================
|
|
|
|
double rtty::nco(double freq)
|
|
{
|
|
phaseacc += TWOPI * freq / samplerate;
|
|
|
|
if (phaseacc > M_PI)
|
|
phaseacc -= TWOPI;
|
|
|
|
return cos(phaseacc);
|
|
}
|
|
|
|
double rtty::FSKnco()
|
|
{
|
|
FSKphaseacc += TWOPI * 1000 / samplerate;
|
|
|
|
if (FSKphaseacc > M_PI)
|
|
|
|
FSKphaseacc -= TWOPI;
|
|
|
|
return sin(FSKphaseacc);
|
|
|
|
}
|
|
|
|
void rtty::send_symbol(int symbol)
|
|
{
|
|
double freq;
|
|
|
|
if (reverse)
|
|
symbol = !symbol;
|
|
|
|
if (symbol)
|
|
freq = get_txfreq_woffset() + shift / 2.0;
|
|
else
|
|
freq = get_txfreq_woffset() - shift / 2.0;
|
|
|
|
for (int i = 0; i < symbollen; i++) {
|
|
outbuf[i] = nco(freq);
|
|
if (symbol)
|
|
FSKbuf[i] = FSKnco();
|
|
else
|
|
FSKbuf[i] = 0.0 * FSKnco();
|
|
}
|
|
|
|
if (progdefaults.PseudoFSK)
|
|
ModulateStereo(outbuf, FSKbuf, symbollen);
|
|
else
|
|
ModulateXmtr(outbuf, symbollen);
|
|
}
|
|
|
|
void rtty::send_stop()
|
|
{
|
|
double freq;
|
|
bool invert = reverse;
|
|
|
|
if (invert)
|
|
freq = get_txfreq_woffset() - shift / 2.0;
|
|
else
|
|
freq = get_txfreq_woffset() + shift / 2.0;
|
|
|
|
for (int i = 0; i < stoplen; i++) {
|
|
outbuf[i] = nco(freq);
|
|
if (invert)
|
|
FSKbuf[i] = 0.0 * FSKnco();
|
|
else
|
|
FSKbuf[i] = FSKnco();
|
|
}
|
|
if (progdefaults.PseudoFSK)
|
|
ModulateStereo(outbuf, FSKbuf, stoplen);
|
|
else
|
|
ModulateXmtr(outbuf, stoplen);
|
|
}
|
|
|
|
void rtty::send_char(int c)
|
|
{
|
|
int i;
|
|
|
|
if (nbits == 5) {
|
|
if (c == LETTERS)
|
|
c = 0x1F;
|
|
if (c == FIGURES)
|
|
c = 0x1B;
|
|
}
|
|
|
|
send_symbol(0);
|
|
// data bits
|
|
for (i = 0; i < nbits; i++) {
|
|
send_symbol((c >> i) & 1);
|
|
}
|
|
// parity bit
|
|
if (rtty_parity != RTTY_PARITY_NONE)
|
|
send_symbol(rttyparity(c));
|
|
// stop bit(s)
|
|
send_stop();
|
|
|
|
if (nbits == 5) {
|
|
if (c == 0x1F || c == 0x1B)
|
|
return;
|
|
if (txmode == LETTERS)
|
|
c = letters[c];
|
|
else
|
|
c = figures[c];
|
|
if (c)
|
|
put_echo_char(progdefaults.rx_lowercase ? tolower(c) : c);
|
|
}
|
|
}
|
|
|
|
void rtty::send_idle()
|
|
{
|
|
|
|
if (nbits == 5) {
|
|
send_char(LETTERS);
|
|
txmode = LETTERS;
|
|
} else
|
|
send_char(0);
|
|
}
|
|
|
|
static int line_char_count = 0;
|
|
|
|
int rtty::tx_process()
|
|
{
|
|
int c;
|
|
|
|
if (preamble > 0) {
|
|
for (int i = 0; i < preamble; i++)
|
|
send_symbol(1);
|
|
if (nbits == 5) {
|
|
send_char(LETTERS);
|
|
txmode = LETTERS;
|
|
}
|
|
preamble = 0;
|
|
}
|
|
|
|
c = get_tx_char();
|
|
|
|
// TX buffer empty
|
|
if (c == 3 || stopflag) {
|
|
stopflag = false;
|
|
line_char_count = 0;
|
|
if (nbits != 5) {
|
|
send_char('\r');
|
|
send_char('\r');
|
|
send_char('\n');
|
|
} else {
|
|
send_char(0x08);
|
|
send_char(0x08);
|
|
send_char(0x02);
|
|
}
|
|
// KeyLine->clearDTR();
|
|
cwid();
|
|
return -1;
|
|
}
|
|
|
|
// send idle character if c == -1
|
|
if (c == -1) {
|
|
send_idle();
|
|
return 0;
|
|
}
|
|
|
|
// if NOT Baudot
|
|
if (nbits != 5) {
|
|
send_char(c);
|
|
return 0;
|
|
}
|
|
|
|
if (isalpha(c) || isdigit(c) || isblank(c) || ispunct(c)) {
|
|
++line_char_count;
|
|
}
|
|
|
|
if (progdefaults.rtty_autocrlf && (c != '\n' && c != '\r') &&
|
|
(line_char_count == progdefaults.rtty_autocount ||
|
|
(line_char_count > progdefaults.rtty_autocount - 5 && c == ' '))) {
|
|
line_char_count = 0;
|
|
if (progdefaults.rtty_crcrlf)
|
|
send_char(0x08); // CR-CR-LF triplet
|
|
send_char(0x08);
|
|
send_char(0x02);
|
|
if (c == ' ')
|
|
return 0;
|
|
}
|
|
if (c == '\r') {
|
|
line_char_count = 0;
|
|
send_char(0x08);
|
|
return 0;
|
|
}
|
|
if (c == '\n') {
|
|
line_char_count = 0;
|
|
if (progdefaults.rtty_crcrlf)
|
|
send_char(0x08); // CR-CR-LF triplet
|
|
send_char(0x02);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* unshift-on-space */
|
|
if (c == ' ') {
|
|
if (progdefaults.UOStx) {
|
|
send_char(LETTERS);
|
|
send_char(0x04); // coded value for a space
|
|
txmode = LETTERS;
|
|
} else
|
|
send_char(0x04);
|
|
return 0;
|
|
}
|
|
|
|
if ((c = baudot_enc(c)) < 0)
|
|
return 0;
|
|
|
|
// switch case if necessary
|
|
|
|
if ((c & 0x300) != txmode) {// == 0) {
|
|
if (txmode == FIGURES) {
|
|
send_char(LETTERS);
|
|
txmode = LETTERS;
|
|
} else {
|
|
send_char(FIGURES);
|
|
txmode = FIGURES;
|
|
}
|
|
}
|
|
|
|
send_char(c & 0x1F);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int rtty::baudot_enc(unsigned char data)
|
|
{
|
|
int i, c, mode;
|
|
|
|
mode = 0;
|
|
c = -1;
|
|
|
|
if (islower(data))
|
|
data = toupper(data);
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
if (data == letters[i]) {
|
|
mode |= LETTERS;
|
|
c = i;
|
|
}
|
|
if (data == figures[i]) {
|
|
mode |= FIGURES;
|
|
c = i;
|
|
}
|
|
if (c != -1)
|
|
return (mode | c);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
char rtty::baudot_dec(unsigned char data)
|
|
{
|
|
int out = 0;
|
|
|
|
switch (data) {
|
|
case 0x1F: /* letters */
|
|
rxmode = LETTERS;
|
|
break;
|
|
case 0x1B: /* figures */
|
|
rxmode = FIGURES;
|
|
break;
|
|
case 0x04: /* unshift-on-space */
|
|
if (progdefaults.UOSrx)
|
|
rxmode = LETTERS;
|
|
return ' ';
|
|
break;
|
|
default:
|
|
if (rxmode == LETTERS)
|
|
out = letters[data];
|
|
else
|
|
out = figures[data];
|
|
break;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|