dl-fldigi/src/thor/thor.cxx

1424 wiersze
34 KiB
C++

// ----------------------------------------------------------------------------
// thor.cxx -- thor modem
//
// Copyright (C) 2008-2012
// David Freese <w1hkj@w1hkj.com>
// John Douyere <vk2eta@gmail.com>
// John Phelps <kl4yfd@gmail.com>
//
// This file is part of fldigi.
//
// 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 <stdlib.h>
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <iomanip>
#include <cmath>
#include <libgen.h>
#include <FL/filename.H>
#include "confdialog.h"
#include "status.h"
#include "thor.h"
#include "trx.h"
#include "fl_digi.h"
#include "filters.h"
#include "misc.h"
#include "sound.h"
#include "thorvaricode.h"
#include "ascii.h"
#include "main.h"
#include "debug.h"
// Enable to enable profiling output for the soft-decision decoder
#define SOFTPROFILE false
//#define SOFTPROFILE true
using namespace std;
char thormsg[80];
char confidence[80];
#include "thor-pic.cxx"
void thor::tx_init(SoundBase *sc)
{
scard = sc;
txstate = TX_STATE_PREAMBLE;
txprevtone = 0;
bitstate = 0;
counter = 0;
txphase = 0;
videoText();
strSecXmtText = progdefaults.THORsecText;
if (strSecXmtText.length() == 0)
strSecXmtText = "fldigi "PACKAGE_VERSION" ";
cptr = 0;
}
void thor::rx_init()
{
synccounter = 0;
symcounter = 0;
met1 = 0.0;
met2 = 0.0;
counter = 0;
phase[0] = 0.0;
currmag = prev1mag = prev2mag = 0.0;
// avgsig = 1e-20;
for (int i = 0; i < THORMAXFFTS; i++)
phase[i+1] = 0.0;
put_MODEstatus(mode);
put_sec_char(0);
syncfilter->reset();
datashreg = 1;
sig = noise = 6;
fec_confidence = 0;
s2n_valid = false;
txstate = TX_STATE_RECEIVE;
state = TEXT;
pic_str = " ";
img_phase = 0.0;
}
void thor::reset_filters()
{
//LOG_INFO("%s", "Reset filters");
// fft filter at first IF frequency
if (fft)
fft->create_filter( (THORFIRSTIF - 0.5 * progdefaults.THOR_BW * bandwidth) / samplerate,
(THORFIRSTIF + 0.5 * progdefaults.THOR_BW * bandwidth)/ samplerate );
for (int i = 0; i < THORMAXFFTS; i++)
if (binsfft[i]) {
delete binsfft[i];
binsfft[i] = 0;
}
if (slowcpu) {
extones = 4;
paths = THORSLOWPATHS;
} else {
extones = THORNUMTONES / 2;
paths = THORFASTPATHS;
}
lotone = basetone - extones * doublespaced;
hitone = basetone + THORNUMTONES * doublespaced + extones * doublespaced;
numbins = hitone - lotone;
//LOG_INFO("MAX ARRAY SIZE %d, paths %d, numbins %d, array_size %d", MAXPATHS, paths, numbins, numbins * paths);
for (int i = 0; i < paths; i++) {
if (binsfft[i]) delete binsfft[i];
binsfft[i] = new sfft (symlen, lotone, hitone);
}
//LOG_INFO("binsfft(%d) initialized", paths);
for (int i = 0; i < THORSCOPESIZE; i++) {
if (vidfilter[i]) delete vidfilter[i];
vidfilter[i] = new Cmovavg(16);
}
//LOG_INFO("vidfilter(%d) initialized", THORSCOPESIZE);
if (syncfilter) delete syncfilter;
syncfilter = new Cmovavg(8);
//LOG_INFO("%s", "syncfilter initialized");
filter_reset = false;
}
void thor::restart()
{
filter_reset = true;
}
void thor::init()
{
//LOG_INFO("%s", "thor::init");
modem::init();
// reset_filters();
rx_init();
imageheader.clear();
avatarheader.clear();
set_scope_mode(Digiscope::DOMDATA);
}
thor::~thor()
{
if (hilbert) delete hilbert;
for (int i = 0; i < THORMAXFFTS; i++) {
if (binsfft[i]) delete binsfft[i];
}
for (int i = 0; i < THORSCOPESIZE; i++) {
if (vidfilter[i]) delete vidfilter[i];
}
if (syncfilter) delete syncfilter;
if (pipe) delete [] pipe;
if (fft) delete fft;
if (Rxinlv) delete Rxinlv;
if (Txinlv) delete Txinlv;
if (Dec) delete Dec;
if (Enc) delete Enc;
delete picfilter;
delete pixfilter;
delete pixsyncfilter;
}
thor::thor(trx_mode md) : hilbert(0), fft(0), filter_reset(false)
{
cap |= CAP_REV;
mode = md;
int isize = 4;
int idepth = 10;
flushlength = 4;
switch (mode) {
// 11.025 kHz modes
case MODE_THOR5:
symlen = 2048;
doublespaced = 2;
samplerate = 11025;
break;
case MODE_THOR11:
cap |= CAP_IMG;
symlen = 1024;
doublespaced = 1;
samplerate = 11025;
break;
case MODE_THOR22:
cap |= CAP_IMG;
symlen = 512;
doublespaced = 1;
samplerate = 11025;
break;
// 8kHz modes
case MODE_THOR4:
symlen = 2048;
doublespaced = 2;
samplerate = 8000;
break;
case MODE_THOR8:
symlen = 1024;
doublespaced = 2;
samplerate = 8000;
break;
case MODE_THOR25x4:
symlen = 320;
doublespaced = 4;
samplerate = 8000;
idepth = 50; // 2 sec interleave
flushlength = 40;
break;
case MODE_THOR50x1:
symlen = 160;
doublespaced = 1;
samplerate = 8000;
idepth = 50; // 1 sec interleave
flushlength = 40;
break;
case MODE_THOR50x2:
symlen = 160;
doublespaced = 2;
samplerate = 8000;
idepth = 50; // 1 sec interleave
flushlength = 40;
break;
case MODE_THOR100:
symlen = 80;
doublespaced = 1;
samplerate = 8000;
idepth = 50; // 0.5 sec interleave
flushlength = 40;
break;
case MODE_THOR16:
default:
cap |= CAP_IMG;
symlen = 512;
doublespaced = 1;
samplerate = 8000;
}
tonespacing = 1.0 * samplerate * doublespaced / symlen;
bandwidth = THORNUMTONES * tonespacing;
hilbert = new C_FIR_filter();
hilbert->init_hilbert(37, 1);
// fft filter at first if frequency
fft = new fftfilt( (THORFIRSTIF - 0.5 * progdefaults.THOR_BW * bandwidth) / samplerate,
(THORFIRSTIF + 0.5 * progdefaults.THOR_BW * bandwidth)/ samplerate,
1024 );
basetone = (int)floor(THORBASEFREQ * symlen / samplerate + 0.5);
slowcpu = progdefaults.slowcpu;
for (int i = 0; i < THORMAXFFTS; i++)
binsfft[i] = 0;
for (int i = 0; i < THORSCOPESIZE; i++)
vidfilter[i] = 0;
syncfilter = 0;
reset_filters();
twosym = 2 * symlen;
pipe = new THORrxpipe[twosym];
scopedata.alloc(THORSCOPESIZE);
videodata.alloc(THORMAXFFTS * numbins );
pipeptr = 0;
symcounter = 0;
metric = 0.0;
fragmentsize = symlen;
s2n = 0.0;
prev1symbol = prev2symbol = 0;
prev1symbol = prev2symbol = 0;
if ( mode == MODE_THOR100 || mode == MODE_THOR50x1 || mode == MODE_THOR50x2 || mode == MODE_THOR25x4 ) {
Enc = new encoder (THOR_K15, K15_POLY1, K15_POLY2);
Dec = new viterbi (THOR_K15, K15_POLY1, K15_POLY2);
Dec->settraceback (PATHMEM-1); // Long constraint length codes require longer traceback
} else {
Enc = new encoder (THOR_K, THOR_POLY1, THOR_POLY2);
Dec = new viterbi (THOR_K, THOR_POLY1, THOR_POLY2);
Dec->settraceback (45);
}
Txinlv = new interleave (isize, idepth, INTERLEAVE_FWD);
Rxinlv = new interleave (isize, idepth, INTERLEAVE_REV);
Dec->setchunksize (1);
bitstate = 0;
symbolpair[0] = symbolpair[1] = 0;
datashreg = 1;
picfilter = new C_FIR_filter();
picfilter->init_lowpass(257, 1, 1.0 * bandwidth / samplerate);
IMAGEspp = THOR_IMAGESPP;
pixfilter = new Cmovavg(IMAGEspp);
pixsyncfilter = new Cmovavg(3*IMAGEspp);
init();
}
//=====================================================================
// rx modules
cmplx thor::mixer(int n, const cmplx& in)
{
double f;
// first IF mixer (n == 0) plus
// THORMAXFFTS mixers are supported each separated by 1/THORMAXFFTS bin size
// n == 1, 2, 3, 4 ... THORMAXFFTS
if (n == 0)
f = frequency - THORFIRSTIF;
else
f = THORFIRSTIF - THORBASEFREQ - bandwidth*0.5 + (samplerate / symlen) * ( (double)n / paths);
cmplx z( cos(phase[n]), sin(phase[n]) );
z *= in;
phase[n] -= TWOPI * f / samplerate;
if (phase[n] < 0) phase[n] += TWOPI;
return z;
}
void thor::s2nreport(void)
{
modem::s2nreport();
s2n_valid = false;
}
void thor::parse_pic(int ch)
{
pic_str.erase(0,1);
pic_str += ch;
b_ava = false;
image_mode = 0;
if (pic_str.find("pic%") == 0) {
switch (pic_str[4]) {
case 'A': picW = 59; picH = 74; b_ava = true; break;
case 'T': picW = 59; picH = 74; break;
case 'S': picW = 160; picH = 120; break;
case 'L': picW = 320; picH = 240; break;
case 'F': picW = 640; picH = 480; break;
case 'V': picW = 640; picH = 480; break;
case 'P': picW = 240; picH = 300; break;
case 'p': picW = 240; picH = 300; image_mode = 1; break;
case 'M': picW = 120; picH = 150; break;
case 'm': picW = 120; picH = 150; image_mode = 1; break;
default: return;
}
} else
return;
if (b_ava)
REQ( thor_clear_avatar );
else
REQ( thor_showRxViewer, pic_str[4]);
image_counter = -symlen / 2;
col = row = rgb = 0;
pixsyncfilter->reset();
pixfilter->reset();
state = IMAGE_START;
}
void thor::recvchar(int c)
{
if (c == -1)
return;
if (c & 0x100)
put_sec_char(c & 0xFF);
else {
parse_pic(c);
put_rx_char(c & 0xFF);
if (progdefaults.Pskmails2nreport && (mailserver || mailclient)) {
if (((c & 0xFF) == SOH) && !s2n_valid) {
// starts collecting s2n from first SOH in stream (since start of RX)
s2n_valid = true;
s2n_sum = s2n_sum2 = s2n_ncount = 0.0;
}
if (s2n_valid) {
s2n_sum += s2n_metric;
s2n_sum2 += (s2n_metric * s2n_metric);
s2n_ncount++;
if ((c & 0xFF) == EOT)
s2nreport();
}
}
}
}
//=============================================================================
// Receive
//=============================================================================
void thor::decodePairs(unsigned char symbol)
{
int c, ch, met;
symbolpair[0] = symbolpair[1];
symbolpair[1] = symbol;
symcounter = symcounter ? 0 : 1;
if (symcounter) return;
c = Dec->decode (symbolpair, &met);
if (c == -1)
return;
if(met < 255 / 2) fec_confidence -= 2 + fec_confidence / 2;
else fec_confidence += 2;
if (fec_confidence < 0) fec_confidence = 0;
if (fec_confidence > 100) fec_confidence = 100;
if(met < 255 / 2) fec_confidence -= 2 + fec_confidence / 2;
else fec_confidence += 2;
if (fec_confidence < 0) fec_confidence = 0;
if (fec_confidence > 100) fec_confidence = 100;
if (progStatus.sqlonoff && metric < progStatus.sldrSquelchValue)
return;
datashreg = (datashreg << 1) | !!c;
if ((datashreg & 7) == 1) {
ch = thorvaridec(datashreg >> 1);
recvchar(ch);
//LOG_INFO("thorvaridec %X = %d", datashreg >> 1, ch);
datashreg = 1;
}
}
void thor::decodesymbol()
{
int c;
double fdiff;//, softmag;
unsigned char symbols[4];
bool outofrange = false;
// Decode the IFK+ sequence, which results in a single nibble
fdiff = currsymbol - prev1symbol;
if (reverse) fdiff = -fdiff;
fdiff /= paths;
fdiff /= doublespaced;
if (fabs(fdiff) > 17) outofrange = true;
c = (int)floor(fdiff + .5); {
if (progdefaults.THOR_PREAMBLE) {
if ( preambledetect(c) ) {
softflushrx(); // Flush the soft rx pipeline with punctures (interleaver & viterbi decoder)
return;
}
}
}
c -= 2;
if (c < 0) c += THORNUMTONES;
if (staticburst == true || outofrange == true) // puncture the code
symbols[3] = symbols[2] = symbols[1] = symbols[0] = 128;
else {
symbols[3] = (c & 1) == 1 ? 255 : 0; c /= 2;
symbols[2] = (c & 1) == 1 ? 255 : 0; c /= 2;
symbols[1] = (c & 1) == 1 ? 255 : 0; c /= 2;
symbols[0] = (c & 1) == 1 ? 255 : 0; c /= 2;
}
Rxinlv->symbols(symbols);
for (int i = 0; i < 4; i++) decodePairs(symbols[i]);
}
void thor::softdecodesymbol()
{
unsigned char one, zero;
int c, nextmag=127, rawdoppler=0;
static int lastc=0, lastmag=0, nowmag=0, prev1rawdoppler=0;
static double lastdoppler=0, nowdoppler=0;
unsigned char lastsymbols[4];
bool outofrange=false;
double fdiff = currsymbol - prev1symbol;
if (reverse) fdiff = -fdiff;
fdiff /= paths;
fdiff /= doublespaced;
c = (int)floor(fdiff + .5); {
if (c < -16 || 0 == c || 1 == c || c > 17) outofrange = true; // Out of the range of the function thor::sendsymbol()
if (progdefaults.THOR_PREAMBLE) {
if ( preambledetect(c) ) {
softflushrx(); // Flush the soft rx pipeline with punctures (interleaver & viterbi decoder)
lastmag = 0;
return;
}
}
#if SOFTPROFILE
LOG_INFO("Symbol: %3d; DELTAf: +%3d",currsymbol, c);
#endif
}
c -= 2;
if (c < 0) c += THORNUMTONES;
// Calculate soft-doppler / frequency-error of the symbol
// For a perfect & undistorted symbol, rawdoppler will == 0 (be a perfect multiple of paths*doublespaced)
rawdoppler = (currsymbol - prev1symbol) % (paths * doublespaced) ;
#if SOFTPROFILE
LOG_INFO("Raw Doppler: %3d", rawdoppler);
#endif
if ( 0 == rawdoppler)
nowdoppler = 1.0f; // Perfect symbol: assign probability = 100%
else {
// Detect modem "de-sync + immediate re-sync" events and reverse the incurred soft-penalty
// Probability of these events increases as baudrate increases
if ( -1 * prev1rawdoppler == rawdoppler) {
rawdoppler = 0;
lastdoppler = 1.0f;
}
// calculate the nowdoppler soft-value as a probability >= 50%
// centering the "50% confidence point" directly between two symbols.
if ( abs(rawdoppler) <= paths * doublespaced / 2 )
nowdoppler = 1.0 - ((1.0 / (paths * doublespaced)) * abs(rawdoppler)) ;
else
nowdoppler = 0.0 + ((1.0 / (paths * doublespaced)) * abs(rawdoppler)) ;
}
prev1rawdoppler = rawdoppler; // save raw-value for comparison on next run
#if SOFTPROFILE
LOG_INFO("Doppler Confidence: %3.1f", nowdoppler);
#endif
// Since the THOR modem is differential, when an outofrange error occurs
// there are 2 possibilities:
// . either previous (reference) symbol or current with a 50%
// probability of each being the culprit.
// Either way, the current symbol is lost, puncture it. There is also a
// 50% probability the next symbol will have an error
if (outofrange){
lastmag /= 2;
nowmag = 0;
nextmag /= 2;
}
// O is puncture/null-symbol from softdecode
if (0 == currsymbol) {
nowmag = 0;
nextmag = 0;
}
// puncture while squelched
if (progStatus.sqlonoff && metric < progStatus.sldrSquelchValue) nowmag = 0;
// One in 16 chance the correct reference tone chosen in staticburst
if (staticburst){
nowmag /= 16;
nextmag /= 16;
}
// Apply the soft-doppler probability to the previous symbol's soft-magnitude
lastmag *= lastdoppler;
if (lastmag <= 0) { // puncture
one = 128;
zero = 128;
}
else if (lastmag > 127) { // limit
one = 255;
zero = 0;
}
else { // Calculate soft bits
one = static_cast<unsigned char>( lastmag+128 ); // never > 255
zero = static_cast<unsigned char>( 127-lastmag ); // never < 0
}
#if SOFTPROFILE
if (outofrange) LOG_INFO("%s","outofrange");
if (staticburst) LOG_INFO("%s","staticburst");
LOG_INFO("next mag %.3d | now mag %.3d | last mag %.3d \n",nextmag, nowmag, lastmag);
#endif
lastsymbols[3] = (lastc & 1) == 1 ? one : zero ; lastc /= 2;
lastsymbols[2] = (lastc & 1) == 1 ? one : zero ; lastc /= 2;
lastsymbols[1] = (lastc & 1) == 1 ? one : zero ; lastc /= 2;
lastsymbols[0] = (lastc & 1) == 1 ? one : zero ; lastc /= 2;
Rxinlv->symbols(lastsymbols);
for (int i = 0; i < 4; i++) decodePairs(lastsymbols[i]);
// Since modem is differential, wait until next symbol (to detect errors)
// then decode.
lastc = c;
lastmag = nowmag;
nowmag = nextmag;
lastdoppler = nowdoppler;
}
int thor::harddecode()
{
double x, max = 0.0;
int symbol = 0;
double avg = 0.0;
static bool cwi[MAXPATHS]; //[paths * numbins];
double cwmag;
for (int i = 0; i < MAXPATHS; i++) cwi[i] = false;
for (int i = 0; i < paths * numbins; i++)
avg += abs(pipe[pipeptr].vector[i]);
avg /= (paths * numbins);
if (avg < 1e-10) avg = 1e-10;
int numtests = 10;
int count = 0;
for (int i = 0; i < paths * numbins; i++) {
cwmag = 0.0;
count = 0;
for (int j = 1; j <= numtests; j++) {
int p = pipeptr - j;
if (p < 0) p += twosym;
cwmag = abs(pipe[j].vector[i])/numtests;
if (cwmag >= 50.0 * (1.0 - progdefaults.ThorCWI) * avg) count++;
}
cwi[i] = (count == numtests);
}
for (int i = 0; i < paths * numbins ; i++) {
if (cwi[i] == false) {
x = abs(pipe[pipeptr].vector[i]);
if (x > max) {
max = x;
symbol = i;
}
} else
LOG_DEBUG("cwi detected in bin %d", i);
}
staticburst = (max / avg < 1.2);
return symbol;
}
int thor::softdecode()
{
static bool lastCWI[MAXPATHS] = {false};
static bool nextCWI[MAXPATHS] = {false};
static const int SoftBailout=6; // Max number of attempts to get a valid symbol
double x, max = 0.0, avg = 0.0;
int symbol = 0;
int avgcount = 0;
int soft_symbol_trycount=0;
int lowest_tone = 0;
int highest_tone = paths * numbins;
// Clear nextCWI bool array for this run
for (int i = 0; i < MAXPATHS; i++)
nextCWI[i] = false;
// for (int i = lowest_tone; i < highest_tone; i++) nextCWI[i] = false;
// Allow for case where symbol == prev2symbol (before calculating signal average...)
if (prev2symbol && (prev2symbol < MAXPATHS - 1)) // array bounds checking
lastCWI[prev2symbol-1] = lastCWI[prev2symbol] = lastCWI[prev2symbol+1] = false;
// Constrict the tone set further so CWI detect & set does not go out of intended range
lowest_tone += 1;
highest_tone -= 1;
// Calculate signal average, ignoring CWI signals
for (int i = lowest_tone; i < highest_tone; i++)
{
if ( !lastCWI[i] ) {
avg += abs(pipe[pipeptr].vector[i]);
avgcount++;
}
}
avg /= avgcount;
if (avg < 1e-10) avg = 1e-10;
// Run symbol-decoder until a non-CWI && non-Repeated symbol is selected
do {
soft_symbol_trycount++;
max = 0.0;
for (int i = lowest_tone; i < highest_tone; i++) {
x = abs(pipe[pipeptr].vector[i]);
if ( x > max && !nextCWI[i-1] && !nextCWI[i] && !nextCWI[i+1] ) {
max = x;
symbol = i;
}
}
if (symbol && (symbol < MAXPATHS - 1)) { // array bounds checking
// detect repeated symbols (an invalid pattern for IFK+ modems)
if ( abs(prev1symbol - symbol) < paths ) {
nextCWI[symbol-1] = nextCWI[symbol] = nextCWI[symbol+1] = true;
}
// Update the next CWI mask if the tone from last-run persists
else if ( lastCWI[symbol-1] || lastCWI[symbol] || lastCWI[symbol+1] ) {
nextCWI[symbol-1] = nextCWI[symbol] = nextCWI[symbol+1] = true;
}
}
} while ( nextCWI[symbol] && soft_symbol_trycount < SoftBailout ); // Run while the detected symbol has been identified as CWI (alt: bailout after 6 trys)
// Copy the newly-detected CWI mask to the static lastCWI array for use on next function call
for (int i = lowest_tone-1; i < highest_tone+1; i++) lastCWI[i] = nextCWI[i];
staticburst = (max / avg < 1.2);
// Return a NULL / Puncture symbol if a bailout occured
if (soft_symbol_trycount >= SoftBailout) return 0;
else return symbol;
}
bool thor::preambledetect(int c)
{
static int preamblecheck=0, twocount=0;
static bool neg16seen=false;
if (twocount > 14 ) twocount = 0;
if (-16 == c && twocount > 2 ) neg16seen = true;
// 2 does not reset the neg16seen bool
else if (2 != c) neg16seen = false;
else if (2 == c) twocount ++;
// -16 does not reset the twos counter
if (-16 != c && 2 != c)
if (twocount > 1) twocount -= 2;
#if SOFTPROFILE
LOG_INFO("[2count:pcheck] [%d:%d]",twocount, preamblecheck);
#endif
if ( twocount > 4 && neg16seen ){
if ( ++preamblecheck > 4 ) return true;
}
else preamblecheck = 0;
return false;
}
// Flush the interleaver and convolutional decoder with punctures
void thor::softflushrx()
{
#if SOFTPROFILE
LOG_INFO("%s", "softflushrx()");
#endif
unsigned char puncture[2], flushsymbols[4];
puncture[0]=128;
puncture[1]=128;
for (int i = 0; i < 4; i++) flushsymbols[i] = 128;
// flush interleaver with punctured symbols
for(int i = 0; i < 90; i++) Rxinlv->symbols(flushsymbols);
// flush viterbi with puncture soft-bits
for (int j = 0; j < 128; j++) Dec->decode (puncture, NULL);
}
void thor::update_syncscope()
{
double max = 0, min = 1e6, range, mag;
memset(videodata, 0, paths * numbins * sizeof(double));
//LOG_INFO("%s", "cleared videodata");
if (!progStatus.sqlonoff || metric >= progStatus.sldrSquelchValue) {
for (int i = 0; i < paths * numbins; i++ ) {
mag = abs(pipe[pipeptr].vector[i]);
if (max < mag) max = mag;
if (min > mag) min = mag;
}
range = max - min;
for (int i = 0; i < paths * numbins; i++ ) {
if (range > 2) {
mag = (abs(pipe[pipeptr].vector[i]) - min) / range + 0.0001;
mag = 1 + 2 * log10(mag);
if (mag < 0) mag = 0;
} else
mag = 0;
videodata[(i + paths * numbins / 2)/2] = 255*mag;
}
}
set_video(videodata, paths * numbins, false);
videodata.next();
memset(scopedata, 0, THORSCOPESIZE * sizeof(double));
if (!progStatus.sqlonoff || metric >= progStatus.sldrSquelchValue) {
for (unsigned int i = 0, j = 0; i < THORSCOPESIZE; i++) {
j = (pipeptr + i * twosym / THORSCOPESIZE + 1) % (twosym);
scopedata[i] = vidfilter[i]->run(abs(pipe[j].vector[prev1symbol]));
}
}
set_scope(scopedata, THORSCOPESIZE);
scopedata.next();
}
void thor::synchronize()
{
double syn = -1;
double val, max = 0.0;
if (staticburst == true) return;
if (currsymbol == prev1symbol)
return;
if (prev1symbol == prev2symbol)
return;
for (unsigned int i = 0, j = pipeptr; i < twosym; i++) {
val = abs(pipe[j].vector[prev1symbol]);
if (val > max) {
max = val;
syn = i;
}
j = (j + 1) % twosym;
}
syn = syncfilter->run(syn);
synccounter += (int) floor(1.0 * (syn - symlen) / THORNUMTONES + 0.5);
update_syncscope();
}
void thor::eval_s2n()
{
double s = abs(pipe[pipeptr].vector[currsymbol]);
double n = (THORNUMTONES - 1) *
abs( pipe[(pipeptr + symlen) % twosym].vector[currsymbol]);
sig = decayavg( sig, s, s - sig > 0 ? 4 : 20);
noise = decayavg( noise, n, 64);
if (noise)
s2n = 20*log10(sig / noise);
else
s2n = 0;
// To partially offset the increase of noise by (THORNUMTONES -1)
// in the noise calculation above,
// add 15*log10(THORNUMTONES -1) = 18.4, and multiply by 6
metric = 6 * (s2n + 18.4);
if (progdefaults.Pskmails2nreport && (mailserver || mailclient)) {
// s2n reporting: re-calibrate
s2n_metric = metric * 2.5 - 70;
s2n_metric = CLAMP(s2n_metric, 0.0, 100.0);
}
metric = metric < 0 ? 0 : metric > 100 ? 100 : metric;
display_metric(metric);
snprintf(thormsg, sizeof(thormsg), "s/n %3.0f dB", s2n );
put_Status1(thormsg);
// Scale FEC indicatior to reduce erratic / jumpy / unreadable display in GUI
int scalefec;
if (fec_confidence++ > 90) scalefec = 100;
else if (fec_confidence++ > 60) scalefec = 75;
else if (fec_confidence++ > 40) scalefec = 50;
else if (fec_confidence++ >= 20) scalefec = 25;
else if ( fec_confidence > 9) scalefec = 10;
else scalefec = 0; // Else just round to 0.
snprintf(confidence, sizeof(confidence), "FEC: %3.1d%%", scalefec);
put_Status2(confidence);
}
void thor::recvpic(double smpl)
{
phidiff = 2.0 * M_PI * frequency / samplerate;
img_phase -= phidiff;
if (img_phase < 0) img_phase += 2.0 * M_PI;
cmplx z = smpl * cmplx( cos(img_phase), sin(img_phase ) );
picfilter->run( z, currz);
double dphase = arg(conj(prevz) * currz);
pixel = (samplerate / TWOPI) * pixfilter->run(dphase);
sync = (samplerate / TWOPI) * pixsyncfilter->run(dphase);
prevz = currz;
//if (image_counter == - (symlen / 2)) std::cout << "IMAGE START\n";
image_counter++;
if (image_counter < 0)
return;
if (state == IMAGE_START) {
if (sync < -0.59 * bandwidth) {
state = IMAGE_SYNC;
//std::cout << "IMAGE SYNC " << image_counter << "\n";
}
return;
}
if (state == IMAGE_SYNC) {
if (sync > -0.51 * bandwidth) {
state = IMAGE;
//std::cout << "IMAGE RECV " << image_counter << "\n";
}
return;
}
if ((image_counter % IMAGEspp) == 0) {
byte = pixel * 256.0 / bandwidth + 128;
byte = (int)CLAMP( byte, 0.0, 255.0);
if (image_mode == 1) { // bw transmission
pixelnbr = 3 * (col + row * picW);
if (b_ava) {
REQ(thor_update_avatar, byte, pixelnbr);
REQ(thor_update_avatar, byte, pixelnbr + 1);
REQ(thor_update_avatar, byte, pixelnbr + 2);
} else {
REQ(thor_updateRxPic, byte, pixelnbr);
REQ(thor_updateRxPic, byte, pixelnbr + 1);
REQ(thor_updateRxPic, byte, pixelnbr + 2);
}
if (++ col == picW) {
col = 0;
row++;
if (row >= picH) {
state = TEXT;
REQ(thor_enableshift);
}
}
} else { // color transmission
pixelnbr = rgb + 3 * (col + row * picW);
if (b_ava)
REQ(thor_update_avatar, byte, pixelnbr);
else
REQ(thor_updateRxPic, byte, pixelnbr);
if (++col == picW) {
col = 0;
if (++rgb == 3) {
rgb = 0;
++row;
}
}
if (row > picH) {
state = TEXT;
REQ(thor_enableshift);
}
}
/*
amplitude *= (samplerate/2)*(.734); // sqrt(3000 / (11025/2))
s2n = 10 * log10(snfilt->run( amplitude * amplitude / noise));
metric = 2 * (s2n + 20);
metric = CLAMP(metric, 0, 100.0); // -20 to +30 db range
display_metric(metric);
amplitude = 0;
*/
}
}
int thor::rx_process(const double *buf, int len)
{
cmplx zref, *zp;
cmplx zarray[1];
int n;
if (slowcpu != progdefaults.slowcpu) {
slowcpu = progdefaults.slowcpu;
filter_reset = true;
}
if (filter_reset) reset_filters();
while (len) {
if (state != TEXT) {
recvpic(*buf);
} else {
// create analytic signal at first IF
zref = cmplx( *buf, *buf );
hilbert->run(zref, zref);
zref = mixer(0, zref);
if (progdefaults.THOR_FILTER && fft) {
// filter using fft convolution
n = fft->run(zref, &zp);
} else {
zarray[0] = zref;
zp = zarray;
n = 1;
}
if (n) {
for (int i = 0; i < n; i++) {
cmplx * pipe_pipeptr_vector = pipe[pipeptr].vector ;
const cmplx zp_i = zp[i];
// process THORMAXFFTS sets of sliding FFTs spaced at 1/THORMAXFFTS bin intervals each of which
// is a matched filter for the current symbol length
for (int k = 0; k < paths; k++) {
// shift in frequency to base band for the sliding DFTs
const cmplx z = mixer(k + 1, zp_i );
// copy current vector to the pipe interleaving the FFT vectors
binsfft[k]->run(z, pipe_pipeptr_vector + k, paths );
}
if (--synccounter <= 0) {
synccounter = symlen;
if (progdefaults.THOR_SOFTSYMBOLS)
currsymbol = softdecode();
else
currsymbol = harddecode();
currmag = abs(pipe_pipeptr_vector[currsymbol]);
eval_s2n();
if (progdefaults.THOR_SOFTBITS)
softdecodesymbol();
else
decodesymbol();
synchronize();
prev2symbol = prev1symbol;
prev1symbol = currsymbol;
prev2mag = prev1mag;
prev1mag = currmag;
}
pipeptr++;
if (pipeptr >= twosym)
pipeptr = 0;
}
}
}
buf++;
--len;
}
return 0;
}
//=============================================================================
// Transmit methods
//=============================================================================
int thor::get_secondary_char()
{
char chr;
if (cptr >= strSecXmtText.length()) cptr = 0;
chr = strSecXmtText[cptr++];
put_sec_char( chr );
return chr;
}
void thor::sendtone(int tone, int duration)
{
double f, phaseincr;
f = (tone + 0.5) * tonespacing + get_txfreq_woffset() - bandwidth / 2;
phaseincr = TWOPI * f / samplerate;
for (int j = 0; j < duration; j++) {
for (int i = 0; i < symlen; i++) {
outbuf[i] = cos(txphase);
txphase -= phaseincr;
if (txphase < 0) txphase += TWOPI;
}
ModulateXmtr(outbuf, symlen);
}
}
void thor::sendsymbol(int sym)
{
cmplx z;
int tone;
tone = (txprevtone + 2 + sym) % THORNUMTONES;
txprevtone = tone;
if (reverse)
tone = (THORNUMTONES - 1) - tone;
sendtone(tone, 1);
}
// Send THOR FEC varicode
void thor::sendchar(unsigned char c, int secondary)
{
const char *code;
code = thorvarienc(c, secondary);
while (*code) {
int data = Enc->encode(*code++ - '0');
for (int i = 0; i < 2; i++) {
bitshreg = (bitshreg << 1) | ((data >> i) & 1);
bitstate++;
if (bitstate == 4) {
Txinlv->bits(&bitshreg);
sendsymbol(bitshreg);
bitstate = 0;
bitshreg = 0;
}
}
}
if (!secondary)
put_echo_char(c);
}
void thor::sendidle()
{
sendchar(0, 0); // <NUL>
}
void thor::sendsecondary()
{
int c = get_secondary_char();
sendchar(c & 0xFF, 1);
}
void thor::Clearbits()
{
int data = Enc->encode(0);
for (int k = 0; k < 1400; k++) {
for (int i = 0; i < 2; i++) {
bitshreg = (bitshreg << 1) | ((data >> i) & 1);
bitstate++;
if (bitstate == 4) {
Txinlv->bits(&bitshreg);
bitstate = 0;
bitshreg = 0;
}
}
}
}
void thor::flushtx()
{
// flush the varicode decoder at the other end
// flush the convolutional encoder and interleaver
for (int i = 0; i < flushlength; i++) sendidle();
bitstate = 0;
}
static bool hide_after_sending = false;
int thor::tx_process()
{
int i = 0;
switch (txstate) {
case TX_STATE_PREAMBLE:
Clearbits();
for (int j = 0; j < 16; j++) sendsymbol(0);
sendidle();
txstate = TX_STATE_START;
break;
case TX_STATE_START:
sendchar('\r', 0);
sendchar(2, 0); // STX
sendchar('\r', 0);
txstate = TX_STATE_DATA;
break;
case TX_STATE_DATA:
if (imageheader.length()) {
txstate = TX_STATE_IMAGE;
break;
}
if (avatarheader.length()) {
txstate = TX_STATE_AVATAR;
break;
}
i = get_tx_char();
if (i == GET_TX_CHAR_NODATA)
sendsecondary();
else if (i == GET_TX_CHAR_ETX)
txstate = TX_STATE_END;
else
sendchar(i, 0);
if (stopflag) {
txstate = TX_STATE_END;
stopflag = false;
}
break;
case TX_STATE_END:
sendchar('\r', 0);
sendchar(4, 0); // EOT
sendchar('\r', 0);
txstate = TX_STATE_FLUSH;
break;
case TX_STATE_FLUSH:
flushtx();
cwid();
txstate = TX_STATE_RECEIVE;
return -1;
case TX_STATE_IMAGE:
for (size_t n = 0; n < imageheader.length(); n++)
sendchar(imageheader[n], 0);
flushtx();
send_image();
if (hide_after_sending) thorpicTxWin->hide();
hide_after_sending = false;
txstate = TX_STATE_DATA;
break;
case TX_STATE_AVATAR:
for (size_t n = 0; n < avatarheader.length(); n++)
sendchar(avatarheader[n], 0);
flushtx();
send_avatar();
txstate = TX_STATE_DATA;
break;
}
return 0;
}
// image support
#define PHASE_CORR 20
void thor::send_image() {
int W = 640, H = 480; // grey scale transfer (FAX)
bool color = true;
float freq, phaseincr;
float radians = 2.0 * M_PI / samplerate;
imageheader.clear();
if (!thorpicTxWin || !thorpicTxWin->visible()) {
return;
}
switch (selthorpicSize->value()) {
case 0 : W = 59; H = 74; break;
case 1 : W = 160; H = 120; break;
case 2 : W = 320; H = 240; break;
case 3 : W = 640; H = 480; color = false; break;
case 4 : W = 640; H = 480; break;
case 5 : W = 240; H = 300; break;
case 6 : W = 240; H = 300; color = false; break;
case 7 : W = 120; H = 150; break;
case 8 : W = 120; H = 150; color = false; break;
}
REQ(thor_clear_tximage);
double black[symlen];
memset(black, 0, sizeof(*black) * symlen);
for (int i = 0; i < PHASE_CORR; i++) ModulateXmtr(black, symlen);
freq = frequency - 0.6 * bandwidth;
phaseincr = radians * freq;
for (int i = 0; i < PHASE_CORR; i++) {
for (int n = 0; n < symlen; n++) {
black[n] = cos(txphase);
txphase -= phaseincr;
if (txphase < 0) txphase += TWOPI;
}
ModulateXmtr(black, symlen);
}
if (color == false) { // grey scale image
for (int row = 0; row < H; row++) {
memset(outbuf, 0, IMAGEspp * sizeof(*outbuf));
for (int col = 0; col < W; col++) {
if (stopflag) return;
tx_pixelnbr = col + row * W;
tx_pixel = 0.3 * thorpic_TxGetPixel(tx_pixelnbr, 0) + // red
0.6 * thorpic_TxGetPixel(tx_pixelnbr, 1) + // green
0.1 * thorpic_TxGetPixel(tx_pixelnbr, 2); // blue
REQ(thor_updateTxPic, tx_pixel, tx_pixelnbr*3 + 0);
REQ(thor_updateTxPic, tx_pixel, tx_pixelnbr*3 + 1);
REQ(thor_updateTxPic, tx_pixel, tx_pixelnbr*3 + 2);
freq = frequency + (tx_pixel - 128) * bandwidth / 256.0;
phaseincr = radians * freq;
for (int n = 0; n < IMAGEspp; n++) {
outbuf[n] = cos(txphase);
txphase -= phaseincr;
if (txphase < 0) txphase += TWOPI;
}
ModulateXmtr(outbuf, IMAGEspp);
Fl::awake();
}
}
} else {
for (int row = 0; row < H; row++) {
for (int color = 0; color < 3; color++) {
memset(outbuf, 0, IMAGEspp * sizeof(*outbuf));
for (int col = 0; col < W; col++) {
if (stopflag) return;
tx_pixelnbr = col + row * W;
tx_pixel = thorpic_TxGetPixel(tx_pixelnbr, color);
REQ(thor_updateTxPic, tx_pixel, tx_pixelnbr*3 + color);
freq = frequency + (tx_pixel - 128) * bandwidth / 256.0;
phaseincr = radians * freq;
for (int n = 0; n < IMAGEspp; n++) {
outbuf[n] = cos(txphase);
txphase -= phaseincr;
if (txphase < 0) txphase += TWOPI;
}
ModulateXmtr(outbuf, IMAGEspp);
}
Fl::awake();
}
}
}
}
void thor::thor_send_image(std::string image_str) {
if (!image_str.empty()) {
hide_after_sending = true;
imageheader = image_str;
}
if (txstate == TX_STATE_RECEIVE)
start_tx();
}
void thor::send_avatar()
{
int W = 59, H = 74;
float freq, phaseincr;
float radians = 2.0 * M_PI / samplerate;
avatarheader.clear();
double black[symlen];
memset(black, 0, sizeof(*black) * symlen);
freq = frequency - 0.6 * bandwidth;
phaseincr = radians * freq;
for (int i = 0; i < PHASE_CORR; i++) ModulateXmtr(black, symlen);
for (int i = 0; i < PHASE_CORR; i++) {
for (int n = 0; n < symlen; n++) {
black[n] = cos(txphase);
txphase -= phaseincr;
if (txphase < 0) txphase += TWOPI;
}
ModulateXmtr(black, symlen);
}
for (int row = 0; row < H; row++) {
for (int color = 0; color < 3; color++) {
memset(outbuf, 0, IMAGEspp * sizeof(*outbuf));
for (int col = 0; col < W; col++) {
if (stopflag) return;
tx_pixelnbr = col + row * W;
tx_pixel = thor_get_avatar_pixel(tx_pixelnbr, color);
freq = frequency + (tx_pixel - 128) * bandwidth / 256.0;
phaseincr = radians * freq;
for (int n = 0; n < IMAGEspp; n++) {
outbuf[n] = cos(txphase);
txphase -= phaseincr;
if (txphase < 0) txphase += TWOPI;
}
ModulateXmtr(outbuf, IMAGEspp);
}
Fl::awake();
}
}
}
void thor::thor_send_avatar() {
if (txstate == TX_STATE_RECEIVE) {
start_tx();
}
}