dl-fldigi/src/feld/feld.cxx

619 wiersze
13 KiB
C++

//
// feld.cxx -- FELDHELL modem
//
// Copyright (C) 2006-2010
// Dave Freese, W1HKJ
//
// This modem code owes much to the efforts of Joe Veldhuis, N8FQ
//
// Adapted from code contained in gmfsk source code distribution.
// gmfsk Copyright (C) 2001, 2002, 2003
// Tomi Manninen (oh2bns@sral.fi)
// Copyright (C) 2004
// Lawrence Glaister (ve7it@shaw.ca)
// ----------------------------------------------------------------------------
// Copyright (C) 2014
// David 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 this program. If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
using namespace std;
#include "feld.h"
#include "fl_digi.h"
#include "fontdef.h"
#include "confdialog.h"
#include "qrunner.h"
#include "status.h"
#include "debug.h"
#include <FL/Fl.H>
#include <FL/Fl_Value_Slider.H>
LOG_FILE_SOURCE(debug::LOG_MODEM);
char feldmsg[80];
int feld::RxColumnLen = FELD_RX_COLUMN_LEN;
int feld::TxColumnLen = 14;
void feld::tx_init(SoundBase *sc)
{
scard = sc;
txcounter = 0.0;
tx_state = PREAMBLE;
preamble = 3;
prevsymb = false;
videoText();
return;
}
void feld::rx_init()
{
rxcounter = 0.0;
peakhold = 0.0;
for (int i = 0; i < 2*RxColumnLen; i++ )
col_data[i] = 0;
col_pointer = 0;
peakhold = 0.0;
minhold = 1.0;
agc = 0.0;
return;
}
void feld::init()
{
modem::init();
initKeyWaveform();
set_scope_mode(Digiscope::BLANK);
put_MODEstatus(mode);
}
void feld::restart()
{
set_bandwidth(hell_bandwidth);
}
feld::~feld()
{
if (hilbert) delete hilbert;
if (bpfilt) delete bpfilt;
if (bbfilt) delete bbfilt;
if (minmaxfilt) delete minmaxfilt;
}
feld::feld(trx_mode m)
{
double lp;
mode = m;
samplerate = FeldSampleRate;
cap |= CAP_BW;
switch (mode) {
// Amplitude modulation modes
case MODE_FELDHELL:
feldcolumnrate = 17.5;
rxpixrate = (RxColumnLen * feldcolumnrate);
txpixrate = (TxColumnLen * feldcolumnrate);
downsampleinc = (double)(rxpixrate/samplerate);
upsampleinc = (double)(txpixrate/samplerate);
hell_bandwidth = txpixrate;
filter_bandwidth = progdefaults.HELL_BW_FH;
break;
case MODE_SLOWHELL:
feldcolumnrate = 2.1875;
rxpixrate = (RxColumnLen * feldcolumnrate);
txpixrate = (TxColumnLen * feldcolumnrate);
downsampleinc = (double)(rxpixrate/samplerate);
upsampleinc = (double)(txpixrate/samplerate);
hell_bandwidth = txpixrate;
filter_bandwidth = progdefaults.HELL_BW_SH;
break;
case MODE_HELLX5:
feldcolumnrate = 87.5;
rxpixrate = (RxColumnLen * feldcolumnrate);
txpixrate = (TxColumnLen * feldcolumnrate);
downsampleinc = (double)(rxpixrate/samplerate);
upsampleinc = (double)(txpixrate/samplerate);
hell_bandwidth = txpixrate;
filter_bandwidth = progdefaults.HELL_BW_X5;
break;
case MODE_HELLX9:
feldcolumnrate = 157.5;
rxpixrate = (RxColumnLen * feldcolumnrate);
txpixrate = (TxColumnLen * feldcolumnrate);
downsampleinc = (double)(rxpixrate/samplerate);
upsampleinc = (double)(txpixrate/samplerate);
hell_bandwidth = txpixrate;
filter_bandwidth = progdefaults.HELL_BW_X9;
break;
// Frequency modulation modes
case MODE_FSKHELL:
feldcolumnrate = 17.5;
rxpixrate = (RxColumnLen * feldcolumnrate);
txpixrate = (TxColumnLen * feldcolumnrate);
downsampleinc = (double)(rxpixrate/samplerate);
upsampleinc = (double)(txpixrate/samplerate);
hell_bandwidth = 122.5;
phi2freq = samplerate / M_PI / (hell_bandwidth / 2.0);
filter_bandwidth = progdefaults.HELL_BW_FSK;
cap |= CAP_REV;
break;
case MODE_FSKH105:
feldcolumnrate = 17.5;
rxpixrate = (RxColumnLen * feldcolumnrate);
txpixrate = (TxColumnLen * feldcolumnrate);
downsampleinc = (double)(rxpixrate/samplerate);
upsampleinc = (double)(txpixrate/samplerate);
hell_bandwidth = 55;
phi2freq = samplerate / M_PI / (hell_bandwidth / 2.0);
filter_bandwidth = progdefaults.HELL_BW_FSK105;
cap |= CAP_REV;
break;
case MODE_HELL80:
feldcolumnrate = 35;
rxpixrate = (RxColumnLen * feldcolumnrate);
txpixrate = (TxColumnLen * feldcolumnrate);
downsampleinc = (double)(rxpixrate/samplerate);
upsampleinc = (double)(txpixrate/samplerate);
hell_bandwidth = 300;
phi2freq = samplerate / M_PI / (hell_bandwidth / 2.0);
filter_bandwidth = progdefaults.HELL_BW_HELL80;
cap |= CAP_REV;
break;
default :
feldcolumnrate = 17.5;
break;
}
progdefaults.HELL_BW = filter_bandwidth;
hilbert = new C_FIR_filter();
hilbert->init_hilbert(37, 1);
set_bandwidth(hell_bandwidth);
wf->redraw_marker();
lp = filter_bandwidth / samplerate;
bpfilt = new fftfilt(0, lp, 1024);
bbfilt = new Cmovavg(8);
average = new Cmovavg( static_cast<int>(500 / downsampleinc));
minmaxfilt = new Cmovavg(120);
blackboard = false;
hardkeying = false;
rxphacc = 0.0;
txphacc = 0.0;
}
// rx section
cmplx feld::mixer(cmplx in)
{
cmplx z;
z = cmplx( cos(rxphacc), sin(rxphacc) );
z = z * in;
rxphacc -= 2.0 * M_PI * frequency / samplerate;
if (rxphacc < 0) rxphacc += TWOPI;
return z;
}
void feld::FSKHELL_rx(cmplx z)
{
double f;
double vid;
double avg;
f = arg(conj(prev) * z) * phi2freq;
prev = z;
f = bbfilt->run(f);
avg = average->run(abs(z));
rxcounter += downsampleinc;
if (rxcounter < 1.0)
return;
rxcounter -= 1.0;
if (avg > agc)
agc = avg;
else
agc *= (1.0 - 0.01 / RxColumnLen);
metric = CLAMP(1000*agc, 0.0, 100.0);
display_metric(metric);
vid = f + 0.5;
vid = CLAMP(vid, 0.0, 1.0);
if (mode == MODE_HELL80)
vid = 1.0 - vid;
if (reverse)
vid = 1.0 - vid;
if (blackboard)
vid = 1.0 - vid;
col_data[col_pointer + RxColumnLen] = (int)(vid * 255.0);
col_pointer++;
if (col_pointer == RxColumnLen) {
if (metric > progStatus.sldrSquelchValue || progStatus.sqlonoff == false) {
REQ(put_rx_data, col_data, col_data.size());
if (!halfwidth)
REQ(put_rx_data, col_data, col_data.size());
}
col_pointer = 0;
for (int i = 0; i < RxColumnLen; i++)
col_data[i] = col_data[i + RxColumnLen];
}
}
void feld::rx(cmplx z)
{
double x, avg;
x = abs(z);
if (x > peakval) peakval = x;
avg = average->run(x);
rxcounter += downsampleinc;
if (rxcounter < 1.0)
return;
rxcounter -= 1.0;
x = peakval;
peakval = 0;
if (x > peakhold)
peakhold = x;
else
peakhold *= (1.0 - 0.02 / RxColumnLen);
x = x / peakhold;
x = CLAMP (x, 0.0, 1.0);
if (avg > agc)
agc = avg;
else
agc *= (1.0 - 0.01 / RxColumnLen);
metric = CLAMP(1000*agc, 0.0, 100.0);
display_metric(metric);
if (blackboard)
x = 255 * x;
else
x = 255 * (1.0 - x);
col_data[col_pointer + RxColumnLen] = (int)x;
col_pointer++;
if (col_pointer == RxColumnLen) {
if (metric > progStatus.sldrSquelchValue || progStatus.sqlonoff == false) {
REQ(put_rx_data, col_data, col_data.size());
if (!halfwidth)
REQ(put_rx_data, col_data, col_data.size());
}
col_pointer = 0;
for (int i = 0; i < RxColumnLen; i++)
col_data[i] = col_data[i + RxColumnLen];
}
}
int feld::rx_process(const double *buf, int len)
{
cmplx z, *zp;
int i, n;
halfwidth = progdefaults.HellRcvWidth;
blackboard = progdefaults.HellBlackboard;
if (progdefaults.HELL_BW != filter_bandwidth) {
double lp;
filter_bandwidth = progdefaults.HELL_BW;
switch (mode) {
case MODE_FELDHELL:
progdefaults.HELL_BW_FH = filter_bandwidth;
break;
case MODE_SLOWHELL:
progdefaults.HELL_BW_SH = filter_bandwidth;
break;
case MODE_HELLX5:
progdefaults.HELL_BW_X5 = filter_bandwidth;
break;
case MODE_HELLX9:
progdefaults.HELL_BW_X9 = filter_bandwidth;
break;
case MODE_FSKHELL:
progdefaults.HELL_BW_FSK = filter_bandwidth;
break;
case MODE_FSKH105:
progdefaults.HELL_BW_FSK105 = filter_bandwidth;
break;
case MODE_HELL80:
progdefaults.HELL_BW_HELL80 = filter_bandwidth;
}
lp = filter_bandwidth / samplerate;
bpfilt->create_filter(0, lp);
wf->redraw_marker();
}
while (len-- > 0) {
/* create analytic signal... */
z = cmplx( *buf, *buf );
buf++;
hilbert->run(z, z);
/* ...so it can be shifted in frequency */
z = mixer(z);
n = bpfilt->run(z, &zp);
switch (mode) {
case MODE_FSKHELL:
case MODE_FSKH105:
case MODE_HELL80:
for (i = 0; i < n; i++) {
FSKHELL_rx(zp[i]);
}
break;
default:
for (i = 0; i < n; i++)
rx(zp[i]);
break;
}
}
return 0;
}
//=====================================================================
// tx section
// returns value = column bits with b0 ... b13 the transmit rows respecfully
// 1 = on, 0 = off
// if all bits are 0
// and no lesser bits are set then character is complete
// then return -1;
int feld::get_font_data(unsigned char c, int col)
{
int bits = 0;
int mask;
int bin;
int ordbits = 0;
fntchr *font = 0;
if (col > 15 || c < ' ' || c > '~')
return -1;
mask = 1 << (15 - col);
switch (fntnbr) {
case 0: font = feld7x7_14; break;
case 1: font = feld7x7n_14; break;
case 2: font = feldDx_14; break;
case 3: font = feldfat_14; break;
case 4: font = feldhell_12; break;
case 5: font = feldlittle_12; break;
case 6: font = feldlo8_14; break;
case 7: font = feldlow_14; break;
case 8: font = feldmodern_14; break;
case 9: font = feldmodern8_14; break;
case 10: font = feldnarr_14; break;
case 11: font = feldreal_14; break;
case 12: font = feldstyl_14; break;
case 13: font = feldvert_14; break;
case 14: font = feldwide_14; break;
default: font = feld7x7_14;
}
for (int i = 0; i < 14; i++) ordbits |= font[c-' '].byte[i];
for (int row = 0; row < 14; row ++) {
bin = font[c - ' '].byte[13 - row] & mask;
if ( bin != 0)
bits |= 1 << row;
}
int testval = (1 << (15 - col)) - 1;
if ( (bits == 0) && ((ordbits & testval) == 0) )
return -1;
return bits;
}
double feld::nco(double freq)
{
double x = sin(txphacc);
txphacc += 2.0 * M_PI * freq / samplerate;
if (txphacc > TWOPI) txphacc -= TWOPI;
return x;
}
void feld::send_symbol(int currsymb, int nextsymb)
{
double tone = get_txfreq_woffset();
double tone2 = 0;
double Amp = 1.0;
double ncoval = 0;
int outlen = 0;
if (mode >= MODE_FSKHELL && mode <= MODE_HELL80) {
tone += (reverse ? -1 : 1) * (prevsymb ? -1 : 1) * bandwidth / 2.0;
tone2 += (reverse ? -1 : 1) * (currsymb ? -1 : 1) * bandwidth / 2.0;
}
for (;;) {
switch (mode) {
ncoval = nco(tone);
case MODE_FSKHELL : case MODE_FSKH105 : case MODE_HELL80 :
if ((tone2 != tone) && ((1.0 - fabs(ncoval)) < .001))
tone = tone2;
break;
case MODE_HELLX5 : case MODE_HELLX9 :
Amp = currsymb;
break;
case MODE_FELDHELL : case MODE_SLOWHELL :
default :
if (prevsymb == 0 && currsymb == 1) {
Amp = OnShape[outlen];
} else if (currsymb == 1 && nextsymb == 0) {
Amp = OffShape[outlen];
} else
Amp = currsymb;
break;
}
outbuf[outlen++] = Amp * nco(tone);
if (outlen >= OUTBUFSIZE) {
LOG_DEBUG("feld reset");
break;
}
txcounter += upsampleinc;
if (txcounter < 1.0)
continue;
txcounter -= 1.0;
break;
}
prevsymb = currsymb;
// write to soundcard & display
ModulateXmtr(outbuf, outlen);
rx_process(outbuf, outlen);
}
void feld::send_null_column()
{
for (int i = 0; i < 14; i++)
send_symbol(0, 0);
}
void feld::tx_char(char c)
{
int column = 0;
int bits, colbits;
int currbit, nextbit;
send_null_column();
if (c == ' ') {
send_null_column();
send_null_column();
send_null_column();
} else {
while ((bits = get_font_data(c, column)) != -1) {
for (int col = 0; col < dxmode; col++) {
colbits = bits;
for (int i = 0; i < 14; i++) {
currbit = colbits & 1;
colbits = colbits >> 1;
nextbit = colbits & 1;
send_symbol(currbit, nextbit);
}
}
column++;
}
}
send_null_column();
return;
}
int feld::tx_process()
{
int c;
bool hdkey;
dxmode = progdefaults.HellXmtWidth;
hdkey = progdefaults.HellPulseFast;
fntnbr = progdefaults.feldfontnbr;
if (hardkeying != hdkey) {
hardkeying = hdkey;
initKeyWaveform();
}
if (tx_state == PREAMBLE) {
if (preamble-- > 0) {
tx_char('.');
return 0;
}
tx_state = DATA;
}
if (tx_state == POSTAMBLE) {
if (postamble-- > 0) {
tx_char('.');
return 0;
}
tx_char(' ');
tx_state = PREAMBLE;
cwid();
return -1;
}
c = get_tx_char();
if (c == GET_TX_CHAR_ETX || stopflag) {
tx_state = POSTAMBLE;
postamble = 3;
return 0;
}
// if TX buffer empty
// send idle character
if (c == GET_TX_CHAR_NODATA) {
if (progdefaults.HellXmtIdle == true)
c = '.';
else {
send_null_column();
send_null_column();
return 0;
}
}
if (c == '\r' || c == '\n')
c = ' ';
tx_char(c);
return 0;
}
void feld::initKeyWaveform()
{
for (int i = 0; i < MAXLEN; i++) {
OnShape[i] = 1.0;
OffShape[i] = 0.0;
}
for (int i = 0; i < 32; i++) {
if (hardkeying == false)
OnShape[i] = 0.5*(1.0 - cos(M_PI * i / 33)); // raised cosine with 4 msec rise
else if (i < 16)
OnShape[i] = 0.5*(1.0 - cos(M_PI * i / 16)); // raised cosine with 2 msec rise
OffShape[31 - i] = OnShape[i];
}
}