dl-fldigi/src/ifkp/ifkp.cxx

825 wiersze
20 KiB
C++

// ----------------------------------------------------------------------------
// ifkp.cxx -- ifkp modem
//
// Copyright (C) 2015
// Dave Freese, W1HKJ
//
// 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 "progress.h"
#include "ifkp.h"
#include "complex.h"
#include "fl_digi.h"
#include "ascii.h"
#include "misc.h"
#include "fileselect.h"
#include "threads.h"
#include "debug.h"
#include "configuration.h"
#include "qrunner.h"
#include "fl_digi.h"
#include "status.h"
#include "main.h"
#include "icons.h"
#include "confdialog.h"
using namespace std;
#include "ifkp_varicode.cxx"
#define IFKP_SR 16000
#include "ifkp-pic.cxx"
static fre_t call("([[:alnum:]]?[[:alpha:]/]+[[:digit:]]+[[:alnum:]/]+)", REG_EXTENDED);
static string teststr = "";
static string allowed = " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/";
static char sz[21];
int ifkp::IMAGEspp = IMAGESPP;
static string valid_callsign(char ch)
{
if (allowed.find(ch) == std::string::npos) ch = ' ';
teststr += tolower(ch);
if (teststr.length() > 20) teststr.erase(0,1);
// wait for ' de '
size_t p1;
if ((p1 = teststr.find(" de ")) != std::string::npos) { // test for callsign
p1 += 4;
if (p1 >= teststr.length()) return "";
while (p1 < teststr.length() && teststr[p1] == ' ') p1++;
if (p1 == teststr.length()) return "";
size_t p2 = teststr.rfind(' ');
if ((p2 > p1) && (p2 - p1 < 21)) { // found a word, test for callsign
memset(sz, 0, 21);
strcpy(sz, teststr.substr(p1, p2-p1).c_str());
teststr.erase(0, p2);
if (call.match(sz)) {
return sz;
}
return "";
}
}
return "";
}
// nibbles table used for fast conversion from tone difference to symbol
static int nibbles[199];
static void init_nibbles()
{
int nibble = 0;
for (int i = 0; i < 199; i++) {
nibble = floor(0.5 + (i - 99.0)/IFKP_SPACING);
// allow for wrap-around (33 tones for 32 tone differences)
if (nibble < 0) nibble += 33;
if (nibble > 32) nibble -= 33;
// adjust for +1 symbol at the transmitter
nibble--;
nibbles[i] = nibble;
}
}
ifkp::ifkp(trx_mode md) : modem()
{
samplerate = IFKP_SR;
symlen = IFKP_SYMLEN;
cap |= CAP_IMG;
if (progdefaults.StartAtSweetSpot) {
frequency = progdefaults.PSKsweetspot;
} else
frequency = wf->Carrier();
REQ(put_freq, frequency);
mode = md;
fft = new g_fft<double>(IFKP_FFTSIZE);
snfilt = new Cmovavg(200);
movavg_size = 4;
for (int i = 0; i < IFKP_NUMBINS; i++) binfilt[i] = new Cmovavg(movavg_size);
txphase = 0;
basetone = 197;
rxfilter = new C_FIR_filter();
float lo = frequency - 0.75 * bandwidth;
float hi = frequency + 0.75 * bandwidth;
rxfilter->init_bandpass(129, 1, lo/samplerate, hi/samplerate);
picfilter = new C_FIR_filter();
picfilter->init_lowpass(257, 1, 1.0 * bandwidth / samplerate);
phase = 0;
phidiff = 2.0 * M_PI * frequency / samplerate;
IMAGEspp = IMAGESPP;
pixfilter = new Cmovavg(IMAGEspp);
ampfilter = new Cmovavg(IMAGEspp);
syncfilter = new Cmovavg(3*IMAGEspp);
bkptr = 0;
peak_counter = 0;
peak = last_peak = 0;
max = 0;
curr_nibble = prev_nibble = 0;
s2n = 0;
memset(rx_stream, 0, sizeof(rx_stream));
rx_text.clear();
for (int i = 0; i < IFKP_BLOCK_SIZE; i++)
a_blackman[i] = blackman(1.0 * i / IFKP_BLOCK_SIZE);
state = TEXT;
init_nibbles();
TX_IMAGE = TX_AVATAR = false;
heard_log_fname = progdefaults.ifkp_heard_log;
std::string sheard = TempDir;
sheard.append(heard_log_fname);
heard_log.open(sheard.c_str(), ios::app);
audit_log_fname = progdefaults.ifkp_audit_log;
std::string saudit = TempDir;
saudit.append(audit_log_fname);
audit_log.open(saudit.c_str(), ios::app);
audit_log << "\n==================================================\n";
audit_log << "Audit log: " << zdate() << ", " << ztime() << "\n";
audit_log << "==================================================\n";
audit_log.flush();
restart();
}
ifkp::~ifkp()
{
delete fft;
delete snfilt;
delete rxfilter;
delete picfilter;
for (int i = 0; i < IFKP_NUMBINS; i++)
delete binfilt[i];
ifkp_deleteTxViewer();
ifkp_deleteRxViewer();
heard_log.close();
audit_log.close();
}
void ifkp::tx_init(SoundBase *sc)
{
scard = sc;
tone = prevtone = 0;
txphase = 0;
send_bot = true;
mycall = progdefaults.myCall;
if (progdefaults.ifkp_lowercase)
for (size_t n = 0; n < mycall.length(); n++) mycall[n] = tolower(mycall[n]);
videoText();
}
void ifkp::rx_init()
{
bkptr = 0;
peak_counter = 0;
peak = last_peak = 0;
max = 0;
curr_nibble = prev_nibble = 0;
s2n = 0;
memset(rx_stream, 0, sizeof(rx_stream));
prevz = cmplx(0,0);
image_counter = 0;
state = TEXT;
rx_text.clear();
for (int i = 0; i < IFKP_NUMBINS; i++) {
tones[i] = 0.0;
binfilt[i]->reset();
}
pixel = 0;
pic_str = " ";
}
void ifkp::init()
{
peak_hits = 4;
mycall = progdefaults.myCall;
if (progdefaults.ifkp_lowercase)
for (size_t n = 0; n < mycall.length(); n++) mycall[n] = tolower(mycall[n]);
movavg_size = 3;
for (int i = 0; i < IFKP_NUMBINS; i++) binfilt[i]->setLength(movavg_size);
rx_init();
}
void ifkp::set_freq(double f)
{
if (progdefaults.ifkp_freqlock)
frequency = 1500;
else
frequency = f;
if (frequency < 100 + 0.5 * bandwidth) frequency = 100 + 0.5 * bandwidth;
if (frequency > 3900 - 0.5 * bandwidth) frequency = 3900 - 0.5 * bandwidth;
tx_frequency = frequency;
REQ(put_freq, frequency);
set_bandwidth(33 * IFKP_SPACING * samplerate / symlen);
basetone = ceil((frequency - bandwidth / 2.0) * symlen / samplerate);
float lo = frequency - 0.75 * bandwidth;
float hi = frequency + 0.75 * bandwidth;
rxfilter->init_bandpass(129, 1, lo/samplerate, hi/samplerate);
picfilter->init_lowpass(257, 1, 1.0 * bandwidth / samplerate);
phase = 0;
phidiff = 2.0 * M_PI * frequency / samplerate;
std::ostringstream it;
it << "\nSamplerate..... " << samplerate;
it << "\nBandwidth...... " << bandwidth;
it << "\nlower cutoff... " << lo;
it << "\nupper cutoff... " << hi;
it << "\ncenter ........ " << frequency;
it << "\nSymbol length.. " << symlen << "\nBlock size..... " << IFKP_SHIFT_SIZE;
it << "\nMinimum Hits... " << peak_hits << "\nBasetone....... " << basetone << "\n";
LOG_VERBOSE("%s", it.str().c_str());
}
void ifkp::show_mode()
{
if (progdefaults.ifkp_baud == 0)
put_MODEstatus("IFKP 0.5");
else if (progdefaults.ifkp_baud == 1)
put_MODEstatus("IFKP 1.0");
else
put_MODEstatus("IFKP 2.0");
return;
}
void ifkp::restart()
{
set_freq(wf->Carrier());
peak_hits = 4;
mycall = progdefaults.myCall;
if (progdefaults.ifkp_lowercase)
for (size_t n = 0; n < mycall.length(); n++) mycall[n] = tolower(mycall[n]);
movavg_size = progdefaults.ifkp_baud == 2 ? 3 : 4;
for (int i = 0; i < IFKP_NUMBINS; i++) binfilt[i]->setLength(movavg_size);
show_mode();
}
// valid printable character
bool ifkp::valid_char(int ch)
{
if ( ! (ch == 10 || ch == 163 || ch == 176 ||
ch == 177 || ch == 215 || ch == 247 ||
(ch > 31 && ch < 128)))
return false;
return true;
}
//=====================================================================
// receive processing
//=====================================================================
void ifkp::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( ifkp_showRxViewer, pic_str[4]);
else
REQ( ifkp_clear_avatar );
image_counter = -symlen / 2;
col = row = rgb = 0;
syncfilter->reset();
pixfilter->reset();
ampfilter->reset();
state = IMAGE_START;
}
void ifkp::process_symbol(int sym)
{
int nibble = 0;
int curr_ch = -1 ;
symbol = sym;
nibble = symbol - prev_symbol;
if (nibble < -99 || nibble > 99) {
prev_symbol = symbol;
return;
}
nibble = nibbles[nibble + 99];
if (nibble >= 0) { // process nibble
curr_nibble = nibble;
// single-nibble characters
if ((prev_nibble < 29) & (curr_nibble < 29)) {
curr_ch = ifkp_varidecode[prev_nibble];
// double-nibble characters
} else if ( (prev_nibble < 29) &&
(curr_nibble > 28) &&
(curr_nibble < 32)) {
curr_ch = ifkp_varidecode[prev_nibble * 32 + curr_nibble];
}
if (curr_ch > 0) {
// if (ch_sqlch_open || metric >= progStatus.sldrSquelchValue) {
if (metric >= progStatus.sldrSquelchValue) {
put_rx_char(curr_ch, FTextBase::RECV);
if (progdefaults.ifkp_enable_audit_log) {
audit_log << ifkp_ascii[curr_ch];
if (curr_ch == '\n') audit_log << '\n';
}
parse_pic(curr_ch);
station_calling = valid_callsign(curr_ch);
if (!station_calling.empty()) {
snprintf(szestimate, sizeof(szestimate), "%.0f db", s2n );
REQ(add_to_heard_list, station_calling.c_str(), szestimate);
if (progdefaults.ifkp_enable_heard_log) {
std::string sheard = zdate();
sheard.append(":").append(ztime());
sheard.append(",").append(station_calling);
sheard.append(",").append(szestimate).append("\n");
heard_log << sheard;
heard_log.flush();
}
}
}
}
prev_nibble = curr_nibble;
}
prev_symbol = symbol;
}
void ifkp::process_tones()
{
noise = 0;
max = 0;
peak = 0;
int firstbin = basetone - 21;
// time domain moving average filter for each tone bin
for (int i = 0; i < IFKP_NUMBINS; ++i) {
val = norm(fft_data[i + firstbin]);
tones[i] = binfilt[i]->run(val);
if (tones[i] > max) {
max = tones[i];
peak = i;
}
}
noise += (tones[0] + tones[IFKP_NUMBINS - 1]) / 2.0;
noise *= IFKP_FFTSIZE;
if (noise < 1e-8) noise = 1e-8;
s2n = 10 * log10(snfilt->run(tones[peak]*.734/noise));
snprintf(szestimate, sizeof(szestimate), "%.0f db", s2n );
metric = 2 * (s2n + 20);
metric = CLAMP(metric, 0, 100.0); // -20 to +30 db range
display_metric(metric);
if (peak == prev_peak) {
peak_counter++;
} else {
peak_counter = 0;
}
if ((peak_counter >= peak_hits) &&
(peak != last_peak) &&
(metric >= progStatus.sldrSquelchValue ||
progStatus.sqlonoff == false)) {
process_symbol(peak);
peak_counter = 0;
last_peak = peak;
}
prev_peak = peak;
}
void ifkp::recvpic(double smpl)
{
phase -= phidiff;
if (phase < 0) phase += 2.0 * M_PI;
cmplx z = smpl * cmplx( cos(phase), sin(phase ) );
picfilter->run( z, currz);
pixel = (samplerate / TWOPI) * pixfilter->run(arg(conj(prevz) * currz));
sync = (samplerate / TWOPI) * syncfilter->run(arg(conj(prevz) * currz));
prevz = currz;
amplitude = ampfilter->run(norm(currz));
image_counter++;
if (image_counter < 0) return;
if (state == IMAGE_START) {
if (sync < -0.59 * bandwidth) {
state = IMAGE_SYNC;
}
return;
}
if (state == IMAGE_SYNC) {
if (sync > -0.51 * bandwidth) {
state = IMAGE;
}
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(ifkp_update_avatar, byte, pixelnbr);
REQ(ifkp_update_avatar, byte, pixelnbr + 1);
REQ(ifkp_update_avatar, byte, pixelnbr + 2);
} else {
REQ(ifkp_updateRxPic, byte, pixelnbr);
REQ(ifkp_updateRxPic, byte, pixelnbr + 1);
REQ(ifkp_updateRxPic, byte, pixelnbr + 2);
}
if (++ col == picW) {
col = 0;
row++;
if (row >= picH) {
state = TEXT;
REQ(ifkp_enableshift);
}
}
} else { // color transmission
pixelnbr = rgb + 3 * (col + row * picW);
if (b_ava)
REQ(ifkp_update_avatar, byte, pixelnbr);
else
REQ(ifkp_updateRxPic, byte, pixelnbr);
if (++col == picW) {
col = 0;
if (++rgb == 3) {
rgb = 0;
++row;
}
}
if (row > picH) {
state = TEXT;
REQ(ifkp_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 ifkp::rx_process(const double *buf, int len)
{
double val;
cmplx zin, z;
if (bkptr < 0) bkptr = 0;
if (bkptr >= IFKP_SHIFT_SIZE) bkptr = 0;
if (progStatus.ifkp_rx_abort) {
state = TEXT;
progStatus.ifkp_rx_abort = false;
REQ(ifkp_clear_rximage);
}
while (len) {
if (state != TEXT) {
recvpic(*buf);
len--;
buf++;
} else {
rxfilter->Irun(*buf, val);
rx_stream[IFKP_BLOCK_SIZE + bkptr] = val;
len--;
buf++;
bkptr++;
if (bkptr == IFKP_SHIFT_SIZE) {
bkptr = 0;
memcpy( rx_stream, // to
&rx_stream[IFKP_SHIFT_SIZE], // from
IFKP_BLOCK_SIZE*sizeof(*rx_stream)); // # bytes
memset(fft_data, 0, sizeof(fft_data));
for (int i = 0; i < IFKP_BLOCK_SIZE; i++) {
double d = rx_stream[i] * a_blackman[i];
fft_data[i] = cmplx(d,d);
}
fft->ComplexFFT(fft_data);
process_tones();
}
}
}
return 0;
}
//=====================================================================
// transmit processing
//=====================================================================
void ifkp::transmit(double *buf, int len)
{
// if (xmtfilt && progdefaults.ifkp_xmtfilter)
// for (int i = 0; i < len; i++) xmtfilt->Irun(buf[i], buf[i]);
ModulateXmtr(buf, len);
}
void ifkp::send_tone(int tone)
{
double phaseincr;
double frequency;
double freq_error = ctrl_freq_offset->value();
frequency = (basetone + tone * IFKP_SPACING) * samplerate / symlen;
if (grpNoise->visible() && btnOffsetOn->value()==true)
frequency += freq_error;
phaseincr = 2.0 * M_PI * frequency / samplerate;
prevtone = tone;
int send_symlen = symlen * (
progdefaults.ifkp_baud == 2 ? 0.5 :
progdefaults.ifkp_baud == 0 ? 2.0 : 1.0);
for (int i = 0; i < send_symlen; i++) {
outbuf[i] = cos(txphase);
txphase -= phaseincr;
if (txphase < 0) txphase += TWOPI;
}
transmit(outbuf, send_symlen);
}
void ifkp::send_symbol(int sym)
{
tone = (prevtone + sym + IFKP_OFFSET) % 33;
send_tone(tone);
}
void ifkp::send_idle()
{
send_symbol(0);
}
void ifkp::send_char(int ch)
{
if (ch <= 0) return send_idle();
int sym1 = ifkp_varicode[ch][0];
int sym2 = ifkp_varicode[ch][1];
send_symbol(sym1);
if (sym2 > 28)
send_symbol(sym2);
put_echo_char(ch);
}
void ifkp::send_avatar()
{
int W = 59, H = 74; // grey scale transfer (FAX)
float freq, phaseincr;
float radians = 2.0 * M_PI / samplerate;
freq = frequency - 0.6 * bandwidth;
#define PHASE_CORR (3 * symlen / 2)
phaseincr = radians * freq;
for (int n = 0; n < PHASE_CORR; n++) {
outbuf[n] = cos(txphase);
txphase -= phaseincr;
if (txphase < 0) txphase += TWOPI;
}
transmit(outbuf, PHASE_CORR);
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 = ifkp_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;
}
transmit(outbuf, IMAGEspp);
}
Fl::awake();
}
}
}
void ifkp::send_image()
{
int W = 640, H = 480; // grey scale transfer (FAX)
bool color = true;
float freq, phaseincr;
float radians = 2.0 * M_PI / samplerate;
if (!ifkppicTxWin || !ifkppicTxWin->visible()) {
return;
}
switch (selifkppicSize->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(ifkp_clear_tximage);
freq = frequency - 0.6 * bandwidth;
#define PHASE_CORR (3 * symlen / 2)
phaseincr = radians * freq;
for (int n = 0; n < PHASE_CORR; n++) {
outbuf[n] = cos(txphase);
txphase -= phaseincr;
if (txphase < 0) txphase += TWOPI;
}
transmit(outbuf, PHASE_CORR);
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 * ifkppic_TxGetPixel(tx_pixelnbr, 0) + // red
0.6 * ifkppic_TxGetPixel(tx_pixelnbr, 1) + // green
0.1 * ifkppic_TxGetPixel(tx_pixelnbr, 2); // blue
REQ(ifkp_updateTxPic, tx_pixel, tx_pixelnbr*3 + 0);
REQ(ifkp_updateTxPic, tx_pixel, tx_pixelnbr*3 + 1);
REQ(ifkp_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;
}
transmit(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 = ifkppic_TxGetPixel(tx_pixelnbr, color);
REQ(ifkp_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;
}
transmit(outbuf, IMAGEspp);
}
Fl::awake();
}
}
}
}
std::string img_str;
void ifkp::ifkp_send_image(std::string image_str) {
img_str = image_str;
TX_IMAGE = true;
start_tx();
}
void ifkp::ifkp_send_avatar() {
TX_AVATAR = true;
start_tx();
}
int ifkp::tx_process()
{
if (send_bot) {
send_bot = false;
send_char(0);
send_char(0);
}
int c = get_tx_char();
// if (c == GET_TX_CHAR_ETX || enable_image) {
if (TX_IMAGE || TX_AVATAR) {
if (img_str.length()) {
for (size_t n = 0; n < img_str.length(); n++)
send_char(img_str[n]);
}
if (TX_IMAGE) send_image();
if (TX_AVATAR) send_avatar();
send_char(0);
stopflag = false;
TX_IMAGE = false;
TX_AVATAR = false;
if (img_str.length()) {
ifkppicTxWin->hide();
img_str.clear();
}
return 0;
}
if ( stopflag || c == GET_TX_CHAR_ETX) { // aborts transmission
send_char(0);
TX_IMAGE = false;
TX_AVATAR = false;
stopflag = false;
return -1;
}
send_char(c);
return 0;
}