// ---------------------------------------------------------------------
// pkt.cxx  --  1200/300/2400 baud AX.25
//
//
// This file is a proposed part of fldigi.  Adapted very liberally from
// rtty.cxx, with many thanks to John Hansen, W2FS, who wrote
// 'dcc.doc' and 'dcc2.doc', GNU Octave, GNU Radio Companion, and finally
// Bartek Kania (bk.gnarf.org) whose 'aprs.c' expository coding style helped
// shape this implementation.
//
// Copyright (C) 2010
//	Dave Freese, W1HKJ
//	Chris Sylvain, KB3CS
//
// 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, write to the
//
//  Free Software Foundation, Inc.
//  51 Franklin Street, Fifth Floor
//  Boston, MA  02110-1301 USA.
//
// ---------------------------------------------------------------------


#include <config.h>
#include <iostream>
using namespace std;

#include "pkt.h"

#include "fl_digi.h"
#include "misc.h"
#include "confdialog.h"
#include "configuration.h"
#include "status.h"

#include "timeops.h"

static char msg1[20];
static char msg2[20];

/***********************************************************************
 * these are redefined as static members of the class
 * in the pkt1200.h file
 * increased number of elements in the PKTBITS to prepare for when
 * HF/VHF/DOUBLE selection is available
 * removed leading _ in names.  Leading underscore is reserved in Linux
 * for the OS and library code.
***********************************************************************/
/*
  1200 -> tones 1200 mark and 2200 Hz space (1700 Hz center)
   300 -> tones 1600 space and 1800 Hz mark (1700 Hz center)
  2400 -> tones 297.5 mark and 2102.5 Hz space (1200 Hz center)
*/
const double	pkt::CENTER[] = {1700, 1700, 1200};
const double	pkt::SHIFT[]  = {1000, 200, 1805};
const int	pkt::BAUD[]   = {1200, 300, 2400};
const int	pkt::BITS[]   = {8, 8, 8};

PKT_MicE_field	pkt::MicE_table[][12][5] =  {
	{
	{ Zero,  Zero, South, P0, East },
	{ One,   Zero, South, P0, East },
	{ Two,   Zero, South, P0, East },
	{ Three, Zero, South, P0, East },
	{ Four,  Zero, South, P0, East },
	{ Five,  Zero, South, P0, East },
	{ Six,   Zero, South, P0, East },
	{ Seven, Zero, South, P0, East },
	{ Eight, Zero, South, P0, East },
	{ Nine,  Zero, South, P0, East },
	{ Invalid, Null, Null, Null, Null },
	{ Invalid, Null, Null, Null, Null }
	},
	{ // ['A'..'K'] + 'L'
	{ Zero,  One,  Null, Null, Null }, // custom A/B/C msg codes
	{ One,   One,  Null, Null, Null },
	{ Two,   One,  Null, Null, Null },
	{ Three, One,  Null, Null, Null },
	{ Four,  One,  Null, Null, Null },
	{ Five,  One,  Null, Null, Null },
	{ Six,   One,  Null, Null, Null },
	{ Seven, One,  Null, Null, Null },
	{ Eight, One,  Null, Null, Null },
	{ Nine,  One,  Null, Null, Null },
	{ Space, One,  Null, Null, Null },
	{ Space, Zero, South, P0, East }
	},
	{ // ['P'..'Z']
	{ Zero,  One,  North, P100, West }, // standard A/B/C msg codes
	{ One,   One,  North, P100, West },
	{ Two,   One,  North, P100, West },
	{ Three, One,  North, P100, West },
	{ Four,  One,  North, P100, West },
	{ Five,  One,  North, P100, West },
	{ Six,   One,  North, P100, West },
	{ Seven, One,  North, P100, West },
	{ Eight, One,  North, P100, West },
	{ Nine,  One,  North, P100, West },
	{ Space, One,  North, P100, West },
	{ Invalid, Null, Null, Null, Null }
	} };

PKT_PHG_table	pkt::PHG_table[] = {
	{ "Omni", 4 },
	{ "NE", 2 },
	{ "E",  1 },
	{ "SE", 2 },
	{ "S",  1 },
	{ "SW", 2 },
	{ "W",  1 },
	{ "NW", 2 },
	{ "N",  1 }
	};

void pkt::tx_init(SoundBase *sc)
{
	scard = sc;

	int scale_factor = (pkt_baud > 1200 ? 2 : 1); // baud rate proportional

	// start each new transmission with MARK tone
	pretone = PKT_MarkBits * scale_factor;
	// number of flags to begin frame
	preamble = PKT_StartFlags * scale_factor;
	// number of flags to end frame
	postamble = PKT_EndFlags * scale_factor;

	if (!lo_tone)  lo_tone = new NCO();
	if (!hi_tone)  hi_tone = new NCO();

	lo_tone->init(pkt_ctrfreq-(pkt_shift/2), 0, PKT_SampleRate);
	hi_tone->init(pkt_ctrfreq+(pkt_shift/2), 0, PKT_SampleRate);

	tx_cbuf = &txbuf[0];
	nr_ones = 0;
	currbit = nostuff = did_pkt_head = false;

	videoText();
}

void pkt::rx_init()
{
	rxstate = PKT_RX_STATE_STOP;
	scounter = 0;

	cbuf = &rxbuf[0]; // init rx buf ptr
}

void pkt::set_freq(double f)
{
	// fixed transmit frequency modem
	if (abs(f - 1700) > 9) {
		if (!modem::freqlocked()) {
			modem::set_freq(pkt_ctrfreq);
			modem::freqlock = true;
		}
		modem::set_freq(f);
		nco_lo->init(f-(pkt_shift/2), 0, PKT_SampleRate);
		nco_hi->init(f+(pkt_shift/2), 0, PKT_SampleRate);
		nco_mid->init(f, 0, PKT_SampleRate);
	}
	else {
		if (modem::freqlocked())
			modem::freqlock = false;
		modem::set_freq(pkt_ctrfreq);
		nco_lo->init(pkt_ctrfreq-(pkt_shift/2), 0, PKT_SampleRate);
		nco_hi->init(pkt_ctrfreq+(pkt_shift/2), 0, PKT_SampleRate);
		nco_mid->init(pkt_ctrfreq, 0, PKT_SampleRate);
	}
}

void pkt::init()
{
	modem::init();
	set_freq(pkt_ctrfreq); // use pkt default center freq. don't use PSKsweetspot.

	rx_init();

	snprintf(msg1, sizeof(msg1), "%4i / %-4.0f", pkt_baud, pkt_shift);
	put_Status1(msg1);
	put_MODEstatus(mode);

	if (progdefaults.PKT_PreferXhairScope)
		set_scope_mode(Digiscope::XHAIRS);
	else
		set_scope_mode(Digiscope::RTTY);

	lo_signal_gain = pow(10, progdefaults.PKT_LOSIG_RXGAIN / 10);
	hi_signal_gain = pow(10, progdefaults.PKT_HISIG_RXGAIN / 10);

	lo_txgain = pow(10, progdefaults.PKT_LOSIG_TXGAIN / 10);
	hi_txgain = pow(10, progdefaults.PKT_HISIG_TXGAIN / 10);

	if (hi_txgain > 1.0 || lo_txgain > 1.0) {
	// renormalize output levels
	// [ modem output recording depends on gain =< 1.0 ]
		double inv;

		if (hi_txgain > lo_txgain)
			inv = 1.0 / hi_txgain;
		else
			inv = 1.0 / lo_txgain;

		lo_txgain *= inv;
		hi_txgain *= inv;
	}

	// leave 10% headroom
	lo_txgain *= 0.9;
	hi_txgain *= 0.9;

#ifndef NDEBUG
	static bool do_once = true;

	/* from TAPR AX.25 v2.2 Specification:
	   2.2.8 Order of Bit Transmission
	   With the exception of the FCS field, all fields of an AX.25 frame shall
	   be sent with each octet's least-significant bit first. The FCS shall be
	   sent most-significant bit first. 
	*/
	// it seems they meant "byte first" in the last sentence above.  sigh.

	 /* useful tools for identifying CRCs
	SRP16 CRC16  http://home.netsurf.de/wolfgang.ehrhardt/crchash_en.html
	 */

	if (!do_once) return;

#undef CRCDEBUG
#ifdef CRCDEBUG
	// first two: http://ecee.colorado.edu/~newhallw/TechDepot/AX25CRC/CRC_for_AX25.pdf
	rxbuf[0] = 'A';
	rxbuf[1] = 'B';
	rxbuf[2] = 'C';
	rxbuf[3] = 0x9f;
	rxbuf[4] = 0x2f; // FCS 0x9F2F (CRC16-X25) 0xF9F4 byte-wise reflected
	checkFCS(&rxbuf[3]);

	/* http://www.lammertbies.nl/comm/info/crc-calculation.html

	   8c4ccc2cac6cec1c9c7609 -> 0x1D0F  (each msg byte is flipped)
	   3132333435363738399067 -> 0x0F2E
	*/

	rxbuf[0] = '1';
	rxbuf[1] = '2';
	rxbuf[2] = '3';
	rxbuf[3] = '4';
	rxbuf[4] = '5';
	rxbuf[5] = '6';
	rxbuf[6] = '7';
	rxbuf[7] = '8';
	rxbuf[8] = '9';
	rxbuf[9] = 0x90;
	rxbuf[10] = 0x6e; // FCS 0x906E (CRC16-X25) 0x0976 bytewise reflected
	checkFCS(&rxbuf[9]);

	// http://www.aerospacesoftware.com/winhexcom.html
	rxbuf[0] = 0x11;
	rxbuf[1] = 0x22;
	rxbuf[2] = 0x33;
	rxbuf[3] = 0x44;
	rxbuf[4] = 0x55;
	rxbuf[5] = 0x66;
	rxbuf[6] = 0x77;
	rxbuf[7] = 0x88;
	rxbuf[8] = 0x99;
	rxbuf[9] = 0x14;
	rxbuf[10] = 0x99; // FCS 0x1499 (CRC16-X25) 0x2899 bytewise reflected
	checkFCS(&rxbuf[9]);

	// next two: http://www.lammertbies.nl/forum/viewtopic.php?t=607
	rxbuf[0] = 0x10; // 7e 08 91 87 44 7e  <- 0x4487 HDLC
	rxbuf[1] = 0x89; // == 7e 10 89 e1 22 7e
	rxbuf[2] = 0x83;
	rxbuf[3] = 0x1f; // CRC16-X25 0x831F reflected is 0xC1F8
	checkFCS(&rxbuf[2]);
	rxbuf[2] = 0x1f;
	rxbuf[3] = 0x83; // set FCS (lo,hi)
	rxbuf[4] = 0x0f;
	rxbuf[5] = 0x47; // magic 0x0F47
	checkFCS(&rxbuf[4]);

	rxbuf[0] = 0x10; // 7e 08 b1 85 65 7e  <- 0x6585 HDLC
	rxbuf[1] = 0x8d; // == 7e 10 8d a1 a6 7e
	rxbuf[2] = 0xc5;
	rxbuf[3] = 0x3b; // CRC16-X25 0xC53B reflected Bwise is 0xA3DC
	checkFCS(&rxbuf[2]);
	rxbuf[2] = 0x3b;
	rxbuf[3] = 0xc5; // set FCS (lo,hi)
	rxbuf[4] = 0x0f;
	rxbuf[5] = 0x47; // magic 0x0F47
	checkFCS(&rxbuf[4]);

	// http://cs.nju.edu.cn/yangxc/dcc_teach/fcs-calc.pdf
	/*	rxbuf[0] = 0x72;
	rxbuf[1] = 0xd3;
	rxbuf[2] = 0x4f;
	rxbuf[3] = 0x3c;
	rxbuf[4] = 0x30; // FCS reflected Bwise is 0x3C30
	*/
	rxbuf = { 0x72, 0xd3, 0x4f, 0x3c, 0x0c };
	checkFCS(&rxbuf[3]);

	rxbuf[0] = 0x01; //DF test
	rxbuf[1] = 0x02;
	rxbuf[2] = 0x03;
	rxbuf[3] = 0x04;
	rxbuf[4] = 0x40;
	rxbuf[5] = 0x80;
	rxbuf[6] = 0x61; // (was) 0xb77b
	rxbuf[7] = 0xee; // 0x61EE is reflected Bw 0x8677
	checkFCS(&rxbuf[6]);

	// http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=75742&start=0
	// (looks like an anonymized AX.25 packet)
	rxbuf[0] = 0x80;
	rxbuf[1] = 0x80;
	rxbuf[2] = 0x80;
	rxbuf[3] = 0x80;
	rxbuf[4] = 0x80;
	rxbuf[5] = 0x80;
	rxbuf[6] = 0xf2;
	rxbuf[7] = 0x9e; // addr char 0x9e>>1 = 0x4f == 'O'
	rxbuf[8] = 0x9e;
	rxbuf[9] = 0x9e;
	rxbuf[10] = 0x9e;
	rxbuf[11] = 0x9e;
	rxbuf[12] = 0x9e;
	rxbuf[13] = 0x61;
	rxbuf[14] = 0x03; // ax.25 flag
	rxbuf[15] = 0xf0; // ax.25 flag
	rxbuf[16] = 0x0b;
	rxbuf[17] = 0x58; // FCS is 0x0B58 (hi,lo) msb->lsb 0xd01a Bwise refl.
	checkFCS(&rxbuf[16]);
	rxbuf[16] = 0x58;
	rxbuf[17] = 0x0b; // set FCS (lo,hi) msb->lsb
	rxbuf[18] = 0x0f;
	rxbuf[19] = 0x47; // magic value 0x0F47 revflipped is 0xF0E2
	// bitflip(0x0F47) = 0xE2F0
	// ~(0xE2F0) = 0x1D0F
	// CRC-CCITT magic value is 0x1D0F
	checkFCS(&rxbuf[18]);
#endif // CRCDEBUG

	if (debug::level == debug::DEBUG_LEVEL) {
		// same anonymized packet from above plus some trash before 1st flag
		rxbuf[0]  = 0x66; rxbuf[1]  = 0x77; rxbuf[2]  = 0x7e; rxbuf[3]  = 0x7e;
		rxbuf[4]  = 0x80; rxbuf[5]  = 0x80; rxbuf[6]  = 0x80; rxbuf[7]  = 0x80;
		rxbuf[8]  = 0x80; rxbuf[9]  = 0x80; rxbuf[10] = 0xf2; rxbuf[11] = 0x9e;
		rxbuf[12] = 0x9e; rxbuf[13] = 0x9e; rxbuf[14] = 0x9e; rxbuf[15] = 0x9e;
		rxbuf[16] = 0x9e; rxbuf[17] = 0x61; rxbuf[18] = 0x03; rxbuf[19] = 0xf0;
		rxbuf[20] = 0x58; rxbuf[21] = 0x0b; rxbuf[22] = 0x7e; rxbuf[23] = 0x00;

		unsigned char *cp, b;

		for (cp = &rxbuf[0]; *cp ; cp++) {
			for (b = 0; b < 8; b++)
			if (*cp & (1 << b))
				rx(true);
			else
				rx(false);
		}

		// "Sun May 24 14:56:03 2009" http://bk.gnarf.org/radio/aprs-20090524-1.txt
		rxbuf[0]  = 0x7e; rxbuf[1]  = 0x82; rxbuf[2]  = 0xa0; rxbuf[3]  = 0xaa;
		rxbuf[4]  = 0x64; rxbuf[5]  = 0x6a; rxbuf[6]  = 0x9c; rxbuf[7]  = 0xe0;
		rxbuf[8]  = 0xa6; rxbuf[9]  = 0x9a; rxbuf[10] = 0x6e; rxbuf[11] = 0x8e;
		rxbuf[12] = 0xb2; rxbuf[13] = 0xa8; rxbuf[14] = 0x60; rxbuf[15] = 0xae;
		rxbuf[16] = 0x92; rxbuf[17] = 0x88; rxbuf[18] = 0x8a; rxbuf[19] = 0x66;
		rxbuf[20] = 0x40; rxbuf[21] = 0x65; rxbuf[22] = 0x03; rxbuf[23] = 0xf0;
		rxbuf[24] = 0x3d; rxbuf[25] = 0x35; rxbuf[26] = 0x35; rxbuf[27] = 0x34;
		rxbuf[28] = 0x39; rxbuf[29] = 0x2e; rxbuf[30] = 0x32; rxbuf[31] = 0x36;
		rxbuf[32] = 0x4e; rxbuf[33] = 0x2f; rxbuf[34] = 0x30; rxbuf[35] = 0x31;
		rxbuf[36] = 0x33; rxbuf[37] = 0x30; rxbuf[38] = 0x37; rxbuf[39] = 0x2e;
		rxbuf[40] = 0x30; rxbuf[41] = 0x33; rxbuf[42] = 0x45; rxbuf[43] = 0x2f;
		rxbuf[44] = 0x2a; rxbuf[45] = 0x2a; rxbuf[46] = 0x4b; rxbuf[47] = 0x45;
		rxbuf[48] = 0x56; rxbuf[49] = 0x4c; rxbuf[50] = 0x41; rxbuf[51] = 0x4e;
		rxbuf[52] = 0x47; rxbuf[53] = 0x45; rxbuf[54] = 0x20; rxbuf[55] = 0x48;
		rxbuf[56] = 0x45; rxbuf[57] = 0x4c; rxbuf[58] = 0x49; rxbuf[59] = 0x50;
		rxbuf[60] = 0x4f; rxbuf[61] = 0x52; rxbuf[62] = 0x54; rxbuf[63] = 0x2a;
		rxbuf[64] = 0x2a; rxbuf[65] = 0x20; rxbuf[66] = 0x7b; rxbuf[67] = 0x55;
		rxbuf[68] = 0x49; rxbuf[69] = 0x56; rxbuf[70] = 0x33; rxbuf[71] = 0x32;
		rxbuf[72] = 0x4e; rxbuf[73] = 0x7d; rxbuf[74] = 0x0d; rxbuf[75] = 0xe8;
		rxbuf[76] = 0x03;

		checkFCS(&rxbuf[75]);
		do_put_rx_char(&rxbuf[75]);
	}

#undef NCODEBUG
#ifdef NCODEBUG
	NCO *osc = new NCO();
	osc->init(1200, 0, PKT_SampleRate);
	for(int i = 0; i < 30; i++) {
		cmplx z = osc->cmplx_sample();
		fprintf(stderr,"  %f	%f\n", z.re, z.im);
	}

	osc->init(2200, 0, PKT_SampleRate);
	for(int i = 0; i < 30; i++) {
		cmplx z = osc->cmplx_sample();
		fprintf(stderr,"  %f	%f\n", z.re, z.im);
	}

	delete osc;
#endif // NCODEBUG

	do_once = false;

#endif // NDEBUG
}

pkt::~pkt()
{
	if (nco_lo) delete nco_lo;
	if (nco_hi) delete nco_hi;
	if (nco_mid) delete nco_mid;

	if (idle_signal_buf)  delete idle_signal_buf;

	if (lo_signal_buf)  delete  lo_signal_buf;
	if (hi_signal_buf)  delete  hi_signal_buf;
	if (mid_signal_buf) delete mid_signal_buf;

	if (signal_buf)  delete signal_buf;

	if (pipe) delete [] pipe;
	if (dsppipe) delete [] dsppipe;

	if (lo_tone) delete lo_tone;
	if (hi_tone) delete hi_tone;
}

void pkt::set_pkt_modem_params(int i)
{
	pkt_baud = BAUD[i];
	pkt_shift = SHIFT[i];
	pkt_nbits = BITS[i];
	pkt_ctrfreq = CENTER[i];

	/**************************************************************
	 SYMBOLLEN is the number of samples in one data bit (aka one symbol)
	 at the current baud rate
	**************************************************************/

	symbollen = (int) floor((double)PKT_SampleRate / pkt_baud + 0.5);

	pkt_startlen =	  4 * symbollen;
	pkt_detectlen =	PKT_DetectLen * symbollen;
	pkt_syncdisplen =	PKT_SyncDispLen * symbollen;
	pkt_idlelen =	PKT_IdleLen * symbollen;
	pkt_startlen =	  2 * pkt_idlelen;

	fragmentsize = symbollen; // modem::fragmentsize -> see modem.h

	// http://users.encs.concordia.ca/~n_goswam/advsg00/advsgtxt/c10digtx_b1_r00.htm
	// BW = Baud + Shift * K  .. K ::= 1.2
	pkt_BW = pkt_baud + pkt_shift * 1.2;
}

void pkt::restart()
{
	if (select_val != progdefaults.PKT_BAUD_SELECT) {
		select_val = progdefaults.PKT_BAUD_SELECT;
		set_pkt_modem_params(select_val);
	}

	snprintf(msg1, sizeof(msg1), "%4i / %-4.0f", pkt_baud, pkt_shift);
	put_Status1(msg1);
	put_MODEstatus(mode);

	if (!nco_lo)  nco_lo = new NCO();
	if (!nco_hi)  nco_hi = new NCO();
	if (!nco_mid) nco_mid = new NCO();

	nco_lo->init(pkt_ctrfreq-(pkt_shift/2), 0, PKT_SampleRate);
	nco_hi->init(pkt_ctrfreq+(pkt_shift/2), 0, PKT_SampleRate);
	nco_mid->init(pkt_ctrfreq, 0, PKT_SampleRate);

	set_freq(pkt_ctrfreq);

	set_bandwidth(pkt_shift); // waterfall tuning box

	wf->redraw_marker();

	if (!idle_signal_buf)
	idle_signal_buf = new double [PKT_IdleLen*PKT_MaxSymbolLen];

	idle_signal_pwr = idle_signal_buf_ptr = 0;
	for (int i = 0; i < pkt_idlelen; i++)
	idle_signal_buf[i] = 0;

	if (!lo_signal_buf)	lo_signal_buf = new cmplx [PKT_MaxSymbolLen];
	if (!hi_signal_buf)	hi_signal_buf = new cmplx [PKT_MaxSymbolLen];
	if (!mid_signal_buf)  mid_signal_buf = new cmplx [PKT_MaxSymbolLen];

	if (!signal_buf)
	signal_buf = new double [PKT_DetectLen*PKT_MaxSymbolLen];

	signal_pwr = signal_buf_ptr = 0;
	signal_gain = 1.0; // 5.0

	for(int i = 0; i < pkt_detectlen; i++)
	signal_buf[i] = 0;

	lo_signal_energy = hi_signal_energy =
	mid_signal_energy = cmplx(0, 0);

	yt_avg = correlate_buf_ptr = 0;

	for(int i = 0; i < symbollen; i++)
	lo_signal_buf[i] = hi_signal_buf[i] =
		mid_signal_buf[i] = cmplx(0, 0);

	if (!pipe)
	pipe = new double [PKT_SyncDispLen*PKT_MaxSymbolLen];
	if (!dsppipe)
	dsppipe = new double [PKT_SyncDispLen*PKT_MaxSymbolLen];

	QIptr = pipeptr = 0;

	// 1024 = 2 * SCBLOCKSIZE ( == MAX_ZLEN )
	for (int i = 0; i < MAX_ZLEN; i++)  
		QI[i] = cmplx(0,0);

	metric = 0.0;
	signal_power = noise_power = power_ratio = snr_avg = 1;

	clear_zdata = true;
}

pkt::pkt(trx_mode md)
{
	cap |= CAP_REV;
	cap &= ~CAP_AFC; // modem::cap

	mode = md; // modem::mode

	samplerate = PKT_SampleRate; // modem::samplerate

	nco_lo = nco_hi = nco_mid = (NCO *)0;

	idle_signal_buf = (double *)0;

	lo_signal_buf = hi_signal_buf = mid_signal_buf = (cmplx *)0;

	signal_buf = (double *)0;

	pipe = dsppipe = (double *)0;

	select_val = -1; // force modem param init

	restart();

	lo_tone = hi_tone = (NCO *)0;
	tx_char_count = MAXOCTETS-3; // leave room for FCS and end-flag

//	init_MicE_table();
//	init_PHG_table();
}

void pkt::update_syncscope()
{
	int j, len = pkt_syncdisplen;

	for (int i = 0; i < len; i++) {
	j = pipeptr - i;
	if (j < 0) j += len;
	dsppipe[i] = pipe[j];
	}
	set_scope(dsppipe, len, false);
}

void pkt::clear_syncscope()
{
	set_scope(0, 0, false);
}

/*
cmplx pkt::mixer(cmplx in)
{
	cmplx 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 pkt::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;
}

unsigned int pkt::computeFCS(unsigned char *head, unsigned char *tail)
{
	unsigned char *c, b, tc;

	// CRC AX.25 Generator mask
	//			 == bitflip(poly(x**16 + x**12 + x**5 + 1))
	//			 == bitflip(0x1021) == bitflip(0001000000100001)
	//			 == 1000010000001000 == 0x8408

	// http://www.ross.net/crc/download/crc_v3.txt

	unsigned int fcsv = 0xFFFF;

	// as in Ross except: shift right instead of left and reflected mask
	// (mirror image because AX.25 is lsb->msb order)

	for(c = head; c < tail; c++) {

	fcsv ^= *c;

	for(b = 0; b < 8; b++) {

		if (fcsv & 0x0001)
		fcsv = (fcsv >> 1) ^ 0x8408;
		else
		fcsv >>= 1;

		fcsv &= 0xFFFF;
	}
	}
	fcsv ^= 0xFFFF;
	// fcsv is now (lo,hi)

	tc = (fcsv & 0xFF00) >> 8;
	fcsv = ((fcsv & 0x00FF) << 8) | tc;
	// fcsv is now (hi,lo)

	return fcsv;
}

// compare AX.25 Frame CheckSum (FCS) value to computed value

bool pkt::checkFCS(unsigned char *cp)
{
	// HDLC frame must be at least one byte plus FCS
	// AX.25 frame must be at least MINOCTETS plus FCS
	if (cp < &rxbuf[MINOCTETS-1])  return false;

	/* http://www.tapr.org/pub_ax25.html

	   transmitted AX.25 FCS is big endian (high byte first)
	   and bit-wise reversed for each byte versus the rest of the frame.

	   because we traverse each byte from lsb->msb
	   while receiving or transmitting
	   -- and --
	   the FCS is transmitted msb->lsb per specification, therefore:

	   >>	the FCS must be in the buffer big endian
	   >>	with a byte-wise bit-reversed ("reflected") order.

	   ... really.
	*/
	/* with more investigating I learn:

	   AX.25 FCS is big endian and bit-wise lsb->msb like all the rest
	   of the octets.

	   so therefore:  the FCS goes in the buffer big endian order.

	   ... just that and no more.
	*/

	unsigned int fcsv_rcvd = (unsigned int)(cp[0] << 8) | cp[1];
	unsigned int fcsv = computeFCS(&rxbuf[1], cp); // begin after leading flag

	LOG_DEBUG("FCS computed %04X %s received %04X", fcsv,
		 (fcsv_rcvd == fcsv ? "==" : "<>"), fcsv_rcvd);

	if (fcsv_rcvd == fcsv)
	return true;

	return false;
}

inline void put_rx_const(const char s[])
{
	unsigned char *p = (unsigned char *) &s[0];
	for( ; *p; p++)  put_rx_char(*p);
}

inline void put_rx_hex(unsigned char c)
{
	char v[3];

	snprintf(&v[0], 3, "%02x", c);

	put_rx_char(v[0]);
	put_rx_char(v[1]);
}

void pkt::expand_Cmp(unsigned char *cpI)
{
	// APRS Spec 1.0.1 Chapter 9 - Compressed Position Report format

	unsigned char *cp, tc, cc;
	unsigned char Cmpbuf[96], *bp = &Cmpbuf[0];
	unsigned char *tbp = bp;

	double Lat, Lon, td;
	bool sign;

	cp = cpI+1; // skip past Symbol Table ID char

	// Latitude as base91 number
	tc = *cp++ - 33;
	Lat = tc * 91 * 91 * 91;  // fourth digit ==> x * 91^3
	tc = *cp++ - 33;
	Lat += tc * 91 * 91;  // third digit ==> x * 91^2
	tc = *cp++ - 33;
	Lat += tc * 91;  // second digit ==> x * 91^1
	tc = *cp++ - 33;
	Lat += tc;  // units digit ==> x * 91^0

	Lat = 90.0 - Lat / 380926.0;  // - ==> S, + ==> N

	// Longitude as base91 number
	tc = *cp++ - 33;
	Lon = tc * 91 * 91 * 91;  // 4th digit
	tc = *cp++ - 33;
	Lon += tc * 91 * 91;  // 3rd digit
	tc = *cp++ - 33;
	Lon += tc * 91;  // 2nd digit
	tc = *cp++ - 33;
	Lon += tc;  // units digit

	Lon = -180.0 + Lon / 190463.0;  // - ==> W, + ==> E

	if (Lat < 0) {
	sign = 1; // has sign (is negative)
	Lat *= -1;
	}
	else  sign = 0;

	td = Lat - floor(Lat);
	cc = snprintf((char *)bp, 3, "%2.f", (Lat - td)); // DD
	bp += cc;
	cc = snprintf((char *)bp, 6, "%05.2f", td*60); // MM.MM
	bp += cc;

	if (sign)  *bp++ = 'S';
	else  *bp++ = 'N';

	*bp++ = ' ';

	if (Lon < 0) {
	sign = 1;
	Lon *= -1;
	}
	else  sign = 0;

	td = Lon - floor(Lon);
	cc = snprintf((char *)bp, 4, "%03.f", (Lon - td)); // DDD
	bp += cc;
	cc = snprintf((char *)bp, 6, "%5.2f", td*60); // MM.MM
	bp += cc;

	if (sign)  *bp++ = 'W';
	else  *bp++ = 'E';

	cp += 1; // skip past Symbol Code char

	if (*cp != ' ') { // still more
	if ((*(cp + 2) & 0x18) == 0x10) { // NMEA source = GGA sentence
		// compressed Altitude uses chars in the same range
		// as CSE/SPD but the Compression Type ID takes precedence
		// when it indicates the NMEA source is a GGA sentence.
		// so check on this one first and CSE/SPD last.
		double Altitude;

		tc = *cp++ - 33;  // 2nd digit
		Altitude = tc * 91;
		tc = *cp++ - 33;
		Altitude += tc;

		// this compressed posit field is not very useful as spec'ed,
		// since it cannot produce a possible negative altitude!
		// the NMEA GGA sentence is perfectly capable of providing
		// a negative altitude value.  Mic-E gets this right.

		// Since the example given in the APRS 1.0.1 Spec uses a value
		// in excess of 10000, this field should be re-spec'ed as a
		// value in meters relative to 10km below mean sea level (just
		// as done in Mic-E).
		Altitude = pow(1.002, Altitude);

		if (progdefaults.PKT_unitsSI)
		cc = snprintf((char *)bp, 11, " %-.1fm", Altitude*0.3048);
		else // units per Spec
		cc = snprintf((char *)bp, 12, " %-.1fft", Altitude);
		bp += cc;
	}
	else if (*cp == '{') { // pre-calculated radio range
		double Range;

		cp += 1; // skip past ID char

		tc = *cp++ - 33; // range
		Range = pow(1.08, (double)tc) * 2;

		if (progdefaults.PKT_unitsSI)
		cc = snprintf((char *)bp, 24, " Est. Range = %-.1fkm", Range*1.609);
		else // units per Spec
		cc = snprintf((char *)bp, 24, " Est. Range = %-.1fmi", Range);
		bp += cc;
	}
	else if (*cp >= '!' && *cp <= 'z') { // compressed CSE/SPD
		int Speed;

		tc = *cp++ - 33; // course
		cc = snprintf((char *)bp, 8, " %03ddeg", tc*4);
		bp += cc;

		tc = *cp++ - 33; // speed
		Speed = (int)floor(pow(1.08, (double)tc) - 1); // 1.08^tc - 1 kts

		if (progdefaults.PKT_unitsSI)
		cc = snprintf((char *)bp, 8, " %03dkph", (int)floor(Speed*1.852+0.5));
		else if (progdefaults.PKT_unitsEnglish)
		cc = snprintf((char *)bp, 8, " %03dmph", (int)floor(Speed*1.151+0.5));
		else // units per Spec
		cc = snprintf((char *)bp, 8, " %03dkts", Speed);
		bp += cc;
	}
	}
	if (progdefaults.PKT_RXTimestamp)
	put_rx_const("		   ");

	put_rx_const(" [Cmp] ");

	for(; tbp < bp; tbp++)  put_rx_char(*tbp);

	put_rx_char('\r');

	if (debug::level >= debug::VERBOSE_LEVEL) {
	cp = cpI+12; // skip to Compression Type ID char

	if (*(cp - 2) != ' ') { // Cmp Type ID is valid
		tbp = bp = &Cmpbuf[0];

		tc = *cp - 33; // T

		cc = snprintf((char *)bp, 4, "%02x:", tc);
		bp += cc;

		strcpy((char *)bp, " GPS Fix = ");
		bp += 11;

		if ((tc & 0x20) == 0x20) {
		strcpy((char *)bp, "old");
		bp += 3;
		}
		else {
		strcpy((char *)bp, "current");
		bp += 7;
		}

		strcpy((char *)bp, ", NMEA Source = ");
		bp += 16;

		switch (tc & 0x18) {
		case 0x00:
		strcpy((char *)bp, "other");
		bp += 5;
		break;
		case 0x08:
		strcpy((char *)bp, "GLL");
		bp += 3;
		break;
		case 0x10:
		strcpy((char *)bp, "GGA");
		bp += 3;
		break;
		case 0x18:
		strcpy((char *)bp, "RMC");
		bp += 3;
		break;
		default:
		strcpy((char *)bp, "\?\?");
		bp += 2;
		break;
		}

		strcpy((char *)bp, ", Cmp Origin = ");
		bp += 15;

		switch (tc & 0x07) {
		case 0x00:
		strcpy((char *)bp, "Compressed");
		bp += 10;
		break;
		case 0x01:
		strcpy((char *)bp, "TNC BText");
		bp += 9;
		break;
		case 0x02:
		strcpy((char *)bp, "Software (DOS/Mac/Win/+SA)");
		bp += 26;
		break;
		case 0x03:
		strcpy((char *)bp, "[tbd]");
		bp += 5;
		break;
		case 0x04:
		strcpy((char *)bp, "KPC3");
		bp += 4;
		break;
		case 0x05:
		strcpy((char *)bp, "Pico");
		bp += 4;
		break;
		case 0x06:
		strcpy((char *)bp, "Other tracker [tbd]");
		bp += 19;
		break;
		case 0x07:
		strcpy((char *)bp, "Digipeater conversion");
		bp += 21;
		break;
		default:
		strcpy((char *)bp, "\?\?");
		bp += 2;
		break;
		}

		if (progdefaults.PKT_RXTimestamp)
		put_rx_const("		   ");

		put_rx_const(" [CmpType] ");

		for(; tbp < bp; tbp++)  put_rx_char(*tbp);

		put_rx_char('\r');
	}
	}
}

void pkt::expand_PHG(unsigned char *cpI)
{
	// APRS Spec 1.0.1 Chapter 6 - Time and Position format
	// APRS Spec 1.0.1 Chapter 7 - PHG Extension format

	bool hasPHG = false;
	unsigned char *cp, tc, cc;
	unsigned char PHGbuf[64], *bp = &PHGbuf[0];
	unsigned char *tbp = bp;

	switch (*cpI) {
	case '!':
	case '=': // simplest posits
	
	cp = cpI+1; // skip past posit ID char

		if (*cp != '/') { // posit not compressed
		cp += 19; // skip past posit data
	}
	else { // posit is compressed
		cp += 1; // skip past compressed posit ID char
		cp += 12; // skip past compressed posit data
	}

	if (strncmp((const char *)cp, "PHG", 3) == 0) { // strings match
		unsigned char ndigits;
		int power, height;
		double gain, range;

		cp += 3; // skip past Data Extension ID chars

		// get span of chars in cp which are only digits
		ndigits = strspn((const char *)cp, "0123456789");

		switch (ndigits) {
		//case 1: H might be larger than '9'. code below will work.
		//  must also check that P.GD are all '0'-'9'
		//break;
		case 4: // APRS Spec 1.0.1 Chapter 7 page 28
		case 5: // PHGR proposed for APRS Spec 1.2
		hasPHG = true;

		tc = *cp++ - '0'; // P
		power = tc * tc; // tc^2
		cc = snprintf((char *)bp, 5, "%dW,", power);
		bp += cc;

		tc = *cp++ - '0'; // H
		*bp++ = ' ';
		if (tc < 30) { // constrain Height to signed 32bit value
			height = 10 * (1 << tc); // 10 * 2^tc

			if (progdefaults.PKT_unitsSI)
			cc = snprintf((char *)bp, 11, "%dm", (int)floor(height*0.3048+0.5));
			else // units per Spec
			cc = snprintf((char *)bp, 12, "%dft", height);
			bp += cc;
		}
		else {
			height = 0;
			strcpy((char *)bp, "-\?\?-");
			bp += 4;
		}
		strcpy((char *)bp, " HAAT,");
		bp += 6;

		tc = *cp++; // G
		gain = pow(10, ((double)(tc - '0') / 10));
		cc = snprintf((char *)bp, 6, " %cdB,", tc);
		bp += cc;

		tc = *cp++ - '0'; // D
		*bp++ = ' ';
		if (tc < 9) {
			strcpy((char *)bp, PHG_table[tc].s);
			bp += PHG_table[tc].l;
		}
		else {
			strcpy((char *)bp, "-\?\?-");
			bp += 4;
		}
		*bp++ = ',';

		range = sqrt(2 * height * sqrt(((double)power / 10) * (gain / 2)));
		if (progdefaults.PKT_unitsSI)
			cc = snprintf((char *)bp, 24, " Est. Range = %-.1fkm", range*1.609);
		else // units per Spec
			cc = snprintf((char *)bp, 24, " Est. Range = %-.1fmi", range);
		bp += cc;

		if (ndigits == 5 && *(cp + 1) == '/') {
			// PHGR: http://www.aprs.org/aprs12/probes.txt
			// '1'-'9' and 'A'-'Z' are actually permissible.
			// does anyone send 10 ('A') or more beacons per hour?
			strcpy((char *)bp, ", ");
			bp += 2;

			tc = *cp++; // R
			cc = snprintf((char *)bp, 14, "%c beacons/hr", tc);
			bp += cc;
		}
		break;
		default: // switch(ndigits)
		break;
		}
	}
	break;
	default: // switch(*cpI)
	break;
	}

	if (hasPHG) {
	if (progdefaults.PKT_RXTimestamp)
		put_rx_const("		   ");

	put_rx_const(" [PHG] ");

	for(; tbp < bp; tbp++)  put_rx_char(*tbp);

	put_rx_char('\r');
	}
}

void pkt::expand_MicE(unsigned char *cpI, unsigned char *cpE)
{
	// APRS Spec 1.0.1 Chapter 10 - Mic-E Data format

	bool isMicE = true;
	bool msgstd = false, msgcustom = false;

	// decoding starts at first AX.25 dest addr
	unsigned char *cp = &rxbuf[1], tc, cc;
	unsigned char MicEbuf[64], *bp = &MicEbuf[0];
	unsigned char *tbp = bp;
	unsigned int msgABC = 0;
	PKT_MicE_field Lat = North, LonOffset = Zero, Lon = West;

	for (int i = 0; i < 3; i++) {
	// remember: AX.25 dest addr chars are shifted left by one
	tc = *cp++ >> 1;

	switch (tc & 0xF0) {
	case 0x30: // MicE_table[0]
		cc = tc - '0';
		if (cc < 10) {
		*bp++ = MicE_table[0][cc][0];
		}
		else  isMicE = false;
		break;
	case 0x40: // MicE_table[1]
		cc = tc - 'A';
		if (cc < 12) {
		bool t = MicE_table[1][cc][1]-'0';
		if (t)  {
			msgABC |= t << (2-i);
			msgcustom = true;
		}
		else  msgABC &= ~(1 << (2-i));
		*bp++ = MicE_table[1][cc][0];
		}
		else  isMicE = false;
		break;
	case 0x50: // MicE_table[2]
		cc = tc - 'P';
		if (cc < 11) {
		msgABC |= (MicE_table[2][cc][1]-'0') << (2-i);
		msgstd = true;
		*bp++ = MicE_table[2][cc][0];
		}
		else  isMicE = false;
		break;
	default:   // Invalid
		isMicE = false;
		break;
	}
	}

	for (int i = 3; i < 6; i++) {
	// remember: AX.25 dest addr chars are shifted left by one
	tc = *cp++ >> 1;

	switch (i) {
	case 3:
		switch (tc & 0xF0) {
		case 0x30: // MicE_table[0]
		cc = tc - '0';
		if (cc < 10) {
			Lat = MicE_table[0][cc][2];
			*bp++ = MicE_table[0][cc][0];
		}
		else  isMicE = false;
		break;
		case 0x40: // MicE_table[1]
		cc = tc - 'A';
		if (cc == 11) {
			Lat = MicE_table[1][cc][2];
			*bp++ = MicE_table[1][cc][0];
		}
		else  isMicE = false;
		break;
		case 0x50: // MicE_table[2]
		cc = tc - 'P';
		if (cc < 11) {
			Lat = MicE_table[2][cc][2];
			*bp++ = MicE_table[2][cc][0];
		}
		else  isMicE = false;
		break;
		default:   // Invalid
		isMicE = false;
		break;
		}
		break;
	case 4:
		switch (tc & 0xF0) {
		case 0x30: // MicE_table[0]
		cc = tc - '0';
		if (cc < 10) {
			LonOffset = MicE_table[0][cc][3];
			*bp++ = MicE_table[0][cc][0];
		}
		else  isMicE = false;
		break;
		case 0x40: // MicE_table[1]
		cc = tc - 'A';
		if (cc == 11) {
			LonOffset = MicE_table[1][cc][3];
			*bp++ = MicE_table[1][cc][0];
		}
		else  isMicE = false;
		break;
		case 0x50: // MicE_table[2]
		cc = tc - 'P';
		if (cc < 11) {
			LonOffset = MicE_table[2][cc][3];
			*bp++ = MicE_table[2][cc][0];
		}
		else  isMicE = false;
		break;
		default:   // Invalid
		isMicE = false;
		break;
		}
		break;
	case 5:
		switch (tc & 0xF0) {
		case 0x30: // MicE_table[0]
		cc = tc - '0';
		if (cc < 10) {
			Lon = MicE_table[0][cc][4];
			*bp++ = MicE_table[0][cc][0];
		}
		else  isMicE = false;
		break;
		case 0x40: // MicE_table[1]
		cc = tc - 'A';
		if (cc == 11) {
			Lon = MicE_table[1][cc][4];
			*bp++ = MicE_table[1][cc][0];
		}
		else  isMicE = false;
		break;
		case 0x50: // MicE_table[2]
		cc = tc - 'P';
		if (cc < 11) {
			Lon = MicE_table[2][cc][4];
			*bp++ = MicE_table[2][cc][0];
		}
		else  isMicE = false;
		break;
		default:   // Invalid
		isMicE = false;
		break;
		}
		break;
	default:   // Invalid
		isMicE = false;
		break;
	}
	}

	if (isMicE) {
	int Speed = 0, Course = 0;

	if (progdefaults.PKT_RXTimestamp)
		put_rx_const("		   ");

	put_rx_const(" [Mic-E] ");

	if (msgstd && msgcustom)
		put_rx_const("Unknown? ");
	else if (msgcustom) {
		put_rx_const("Custom-");
		put_rx_char((7 - msgABC)+'0');
		put_rx_const(". ");
	}
	else {
		switch (msgABC) { // APRS Spec 1.0.1 Chapter 10 page 45
		case 0:
		put_rx_const("Emergency");
		break;
		case 1:
		put_rx_const("Priority");
		break;
		case 2:
		put_rx_const("Special");
		break;
		case 3:
		put_rx_const("Committed");
		break;
		case 4:
		put_rx_const("Returning");
		break;
		case 5:
		put_rx_const("In Service");
		break;
		case 6:
		put_rx_const("En Route");
		break;
		case 7:
		put_rx_const("Off Duty");
		break;
		default:
		put_rx_const("-\?\?-");
		break;
		}
		if (msgABC)  put_rx_char('.');
		else  put_rx_char('!'); // Emergency!

		put_rx_char(' ');
	}

	for (; tbp < bp; tbp++) {
		put_rx_char(*tbp);
		if (tbp == (bp - 3))  put_rx_char('.');
	}

	if (Lat == North)  put_rx_char('N');
	else if (Lat == South)  put_rx_char('S');
	else  put_rx_char('\?');

	put_rx_char(' ');

	cp = cpI+1; // one past the Data Type ID char

	// decode Lon degrees - APRS Spec 1.0.1 Chapter 10 page 48
	tc = *cp++ - 28;
	if (LonOffset == P100) tc += 100;
	if (tc > 179 && tc < 190)  tc -= 80;
	else if (tc > 189 && tc < 200)  tc -= 190;

	cc = snprintf((char *)bp, 4, "%03d", tc);
	bp += cc;

	// decode Lon minutes
	tc = *cp++ - 28;
	if (tc > 59)  tc -= 60;

	cc = snprintf((char *)bp, 3, "%02d", tc);
	bp += cc;

	// decode Lon hundredths of a minute
	tc = *cp++ - 28;

	cc = snprintf((char *)bp, 3, "%02d", tc);
	bp += cc;

	for (; tbp < bp; tbp++) {
		put_rx_char(*tbp);
		if (tbp == (bp - 3))  put_rx_char('.');
	}

	if (Lon == East)  put_rx_char('E');
	else if (Lon == West)  put_rx_char('W');
	else  put_rx_char('\?');

	// decode Speed and Course - APRS Spec 1.0.1 Chapter 10 page 52
	tc = *cp++ - 28; // speed: hundreds and tens

	if (tc > 79)  tc -= 80;
	Speed = tc * 10;

	tc = *cp++ - 28; // speed: units and course: hundreds

	Course = (tc % 10); // remainder from dividing by 10
	tc -= Course; tc /= 10; // tc is now quotient from dividing by 10
	Speed += tc;

	if (Course > 3)  Course -= 4;
	Course *= 100;

	tc = *cp++ - 28; // course: tens and units

	Course += tc;

	if (progdefaults.PKT_unitsSI)
		cc = snprintf((char *)bp, 8, " %03dkph", (int)floor(Speed*1.852+0.5));
	else if (progdefaults.PKT_unitsEnglish)
		cc = snprintf((char *)bp, 8, " %03dmph", (int)floor(Speed*1.151+0.5));
	else // units per Spec
		cc = snprintf((char *)bp, 8, " %03dkts", Speed);
	bp += cc;

	cc = snprintf((char *)bp, 8, " %03ddeg", Course);
	bp += cc;

	for (; tbp < bp; tbp++) {
		put_rx_char(*tbp);
	}

	cp += 2; // skip past Symbol and Symbol Table ID chars

	if (cp <= cpE) { // still more

		if (*cp == '>') {
		cp += 1;
		put_rx_const(" TH-D7");
		}
		else if (*cp == ']' && *cpE == '=') {
		cp += 1;
		cpE -= 1;
		put_rx_const(" TM-D710");
		}
		else if (*cp == ']') {
		cp += 1;
		put_rx_const(" TM-D700");
		}
		else if (*cp == '\'' && *(cpE - 1) == '|' && *cpE == '3') {
		cp += 1;
		cpE -= 2;
		put_rx_const(" TT3");
		}
		else if (*cp == '\'' && *(cpE - 1) == '|' && *cpE == '4') {
		cp += 1;
		cpE -= 2;
		put_rx_const(" TT4");
		}
		else if (*cp == '`' && *(cpE - 1) == '_' && *cpE == ' ') {
		cp += 1;
		cpE -= 2;
		put_rx_const(" VX-8");
		}
		else if (*cp == '`' && *(cpE - 1) == '_' && *cpE == '#') {
		cp += 1;
		cpE -= 2;
		put_rx_const(" VX-8D/G"); // VX-8G for certain. guessing.
		}
		else if (*cp == '`' && *(cpE - 1) == '_' && *cpE == '\"') {
		cp += 1;
		cpE -= 2;
		put_rx_const(" FTM-350");
		}
		else if ((*cp == '\'' || *cp == '`') && *(cp + 4) == '}') {
		cp += 1;
		// tracker? rig? ID codes are somewhat ad hoc.
		put_rx_const(" MFR\?");
		}

		if (cp < cpE) {
		if (*(cp + 3) == '}') { // station altitude as base91 number
			int Altitude = 0;

			tc = *cp++ - 33; // third digit ==> x * 91^2
			Altitude = tc * 91 * 91;
			tc = *cp++ - 33; // second digit ==> x * 91^1 ==> x * 91
			Altitude += tc * 91;
			tc = *cp++ - 33; // unit digit ==> x * 91^0 ==> x * 1
			Altitude += tc;

			Altitude -= 10000; // remove offset from datum

			*bp++ = ' ';
			if (Altitude >= 0)  *bp++ = '+';

			if (progdefaults.PKT_unitsEnglish)
			cc = snprintf((char *)bp, 12, "%dft", (int)floor(Altitude*3.281+0.5));
			else // units per Spec
			cc = snprintf((char *)bp, 11, "%dm", Altitude);
			bp += cc;

			for (; tbp < bp; tbp++) {
			put_rx_char(*tbp);
			}

			cp += 1; // skip past '}'
		}
		}

		if (cp < cpE)  put_rx_char(' ');

		for (; cp <= cpE; cp++)  put_rx_char(*cp);
	}

	put_rx_char('\r');
	}
}

void pkt::do_put_rx_char(unsigned char *cp)
{
	int i, j;
	unsigned char c;
	bool isMicE = false;
	unsigned char *cpInfo;

	for (i = 8; i < 14; i++) { // src callsign is second in AX.25 frame
	c = rxbuf[i] >> 1;
	if (c != ' ') put_rx_char(c); // skip past padding (if any)
	}

	// bit  7   = command/response bit
	// bits 6,5 = 1
	// bits 4-1 = src SSID
	// bit  0   = last callsign flag
	c = (rxbuf[14] & 0x7f) >> 1;

	if (c > 0x30) {
	put_rx_char('-');
	if (c < 0x3a)
		put_rx_char(c);
	else {
		put_rx_char('1');
		put_rx_char(c-0x0a);
	}
	}

	put_rx_char('>');

	for (i = 1; i < 7; i++) { // dest callsign is first in AX.25 frame
	c = rxbuf[i] >> 1;
	if (c != ' ') put_rx_char(c);
	}

	c = (rxbuf[7] & 0x7f) >> 1;
	if (c > 0x30) {
	put_rx_char('-');
	if (c < 0x3a)
		put_rx_char(c);
	else {
		put_rx_char('1');
		put_rx_char(c-0x0a);
	}
	}

	j=8;
	if ((rxbuf[14] & 0x01) != 1) { // check last callsign flag
	do {
		put_rx_char(',');

		j += 7;
		for (i = j; i < (j+6); i++) {
		c = rxbuf[i] >> 1;
		if (c != ' ') put_rx_char(c);
		}

		c = (rxbuf[j+6] & 0x7f) >> 1;
		if (c > 0x30) {
		put_rx_char('-');
		if (c < 0x3a)
			put_rx_char(c);
		else {
			put_rx_char('1');
			put_rx_char(c-0x0a);
		}
		}

	} while ((rxbuf[j+6] & 0x01) != 1);

	if (rxbuf[j+6] & 0x80) // packet gets no more hops
		put_rx_char('*');
	}

	if (debug::level < debug::VERBOSE_LEVEL) {
	// skip past CTRL and PID to INFO bytes when I_FRAME
	// puts buffer pointer in FCS when U_FRAME and S_FRAME
	// (save CTRL byte for possible MicE decoding)
	j += 7;
	c = rxbuf[j];
	j += 2;
	}
	else { // show more frame info when .ge. VERBOSE debug level
	j += 7;
	put_rx_char(';');

	c = rxbuf[j]; // CTRL present in all frames

	if ((c & 0x01) == 0) { // I_FRAME
		unsigned char p = rxbuf[j+1]; // PID present only in I_FRAME

		if (debug::level == debug::DEBUG_LEVEL) {
		put_rx_hex(c);
		put_rx_char(' ');
		put_rx_hex(p);
		put_rx_char(';');
		}

		put_rx_const("I/");

		put_rx_hex( (c & 0xE0) >> 5 ); // AX.25 v2.2 para 2.3.2.1
		if (c & 0x10)  put_rx_char('*'); // P/F bit
		else  put_rx_char('.');
		put_rx_hex( (c & 0x0E) >> 1 );

		put_rx_char('/');

		switch (p) { // AX.25 v2.2 para 2.2.4
		case 0x01:
		put_rx_const("X.25PLP");
		break;
		case 0x06:
		put_rx_const("C-TCPIP");
		break;
		case 0x07:
		put_rx_const("U-TCPIP");
		break;
		case 0x08:
		put_rx_const("FRAG");
		break;
		case 0xC3:
		put_rx_const("TEXNET");
		break;
		case 0xC4:
		put_rx_const("LQP");
		break;
		case 0xCA:
		put_rx_const("ATALK");
		break;
		case 0xCB:
		put_rx_const("ATALK-ARP");
		break;
		case 0xCC:
		put_rx_const("ARPA-IP");
		break;
		case 0xCD:
		put_rx_const("ARPA-AR");
		break;
		case 0xCE:
		put_rx_const("FLEXNET");
		break;
		case 0xCF:
		put_rx_const("NET/ROM");
		break;
		case 0xF0:
		put_rx_const("NO-L3");
		break;

		case 0xFF:
		put_rx_const("L3ESC=");
		put_rx_hex(rxbuf[++j]);
		break;

		default:
		if ((p & 0x30) == 0x10)  put_rx_const("L3V1");
		else if ((p & 0x30) == 0x20)  put_rx_const("L3V2");
		else  put_rx_const("L3-RSVD");

		put_rx_char('=');
		put_rx_hex(p);
		break;
		}
	}
	else if ((c & 0x03) == 0x01) { // S_FRAME

		if (debug::level == debug::DEBUG_LEVEL) {
		put_rx_hex(c);
		put_rx_char(';');
		}

		put_rx_const("S/");

		put_rx_hex( (c & 0xE0) >> 5 );
		if (c & 0x10)  put_rx_char('*');
		else  put_rx_char('.');
		put_rx_char('/');

		switch (c & 0x0C) { // AX.25 v2.2 para 2.3.4.2
		case 0x00:
		put_rx_const("RR");
		break;
		case 0x04:
		put_rx_const("RNR");
		break;
		case 0x08:
		put_rx_const("REJ");
		break;
		case 0x0C:
		default:
		put_rx_const("UNK");
		break;
		}
	}
	else if ((c & 0x03) == 0x03) { // U_FRAME

		if (debug::level == debug::DEBUG_LEVEL) {
		put_rx_hex(c);
		put_rx_char(';');
		}

		put_rx_char('U');

		if (c & 0x10)  put_rx_char('*');
		else  put_rx_char('.');

		switch (c & 0xEC) { // AX.25 v2.2 para 2.3.4.3
		case 0x00:
		put_rx_const("UI");
		break;
		case 0x0E:
		put_rx_const("DM");
		break;
		case 0x1E:
		put_rx_const("SABM");
		break;
		case 0x20:
		put_rx_const("DISC");
		break;
		case 0x30:
		put_rx_const("UA");
		break;
		case 0x81:
		put_rx_const("FRMR");
		break;
		default:
		put_rx_const("UNK");
		break;
		}
	}
	j+=2;
	}
	put_rx_char(':');

	// ptr to first info field char
	cpInfo = &rxbuf[j];

	if ((c & 0x03) == 0x03 && (c & 0xEC) == 0x00
	&& (*cpInfo == '\'' || *cpInfo == '`' 
		|| *cpInfo == 0x1C || *cpInfo == 0x1D)
	&& (cp - cpInfo) > 7) {
	/*
	  Mic-E must have at least 8 info chars + Data Type ID char
	  cp - (cpInfo - 1) > 8 ==> cp - cpInfo > 7
	*/
	// this is very probably a Mic-E encoded packet
	isMicE = true;
	}

	// offset between last info char (not FCS) and bufhead
	i = (cp - &rxbuf[0]); // (cp - &rxbuf[1]) + 1 ==> (cp - &rxbuf[0])

	while (j < i)  put_rx_char(rxbuf[j++]);

	if (*(cp-1) != '\r')
	put_rx_char('\r'); // <cr> only for packets not ending with <cr>

	// cp points to FCS, so (cp-X) is last info field char
	if ((progdefaults.PKT_expandMicE || debug::level >= debug::VERBOSE_LEVEL)
	&& isMicE)
	expand_MicE(cpInfo, (*(cp-1) == '\r' ? cp-2 : cp-1));

	// need to deal with posits having timestamps ('/' and '@' leading char)
	if (*cpInfo == '!' || *cpInfo == '=') {
	if ((progdefaults.PKT_expandCmp || debug::level >= debug::VERBOSE_LEVEL)
		 && (*(cpInfo + 1) == '/' || *(cpInfo + 1) == '\\'))
		// compressed posit
		expand_Cmp(cpInfo+1);

	if (progdefaults.PKT_expandPHG
		|| debug::level >= debug::VERBOSE_LEVEL) // look for PHG data
		expand_PHG(cpInfo);
	}

	if (*(cp-1) == '\r')
	put_rx_char('\r'); // for packets ending with <cr>: show it on-screen
}


void pkt::rx(bool bit)
{
	static unsigned char c = 0, bcounter = 0;

	c >>= 1;
	++bcounter;

	if (bit == false) {
	c &= ~(1 << (pkt_nbits-1)); // bits are sent lsb first

	if (seq_ones == 6) { // flag byte found
		bcounter = 0;

		if (cbuf >= &rxbuf[MINOCTETS]) { // flag at end of frame

		*cbuf = PKT_Flag; // == 0x7e

		if (debug::level == debug::DEBUG_LEVEL)
			fprintf(stderr,"7e\n");

		//   monitor Metric and Squelch Level
		if ( !progStatus.sqlonoff ||
			 metric >= progStatus.sldrSquelchValue ) { // lazy eval
			/*
			// check FCS at end of frame
			// if FCS is OK - packet is in rxbuffer,
			//		  put_rx_char() for each byte in rxbuffer
			*/
			if (checkFCS((cbuf - 2)) == true) {
			if (progdefaults.PKT_RXTimestamp) {
				unsigned char ts[16], *tc = &ts[0];
				time_t t = time(NULL);
				struct tm stm;

				(void)gmtime_r(&t, &stm);
				snprintf((char *)ts, sizeof(ts),
					 "[%02d:%02d:%02d] ",
					 stm.tm_hour, stm.tm_min, stm.tm_sec);

				while (*tc)  put_rx_char(*tc++);
			}
			do_put_rx_char((cbuf - 2));
			}
		}

		cbuf = &rxbuf[0]; // reset after first end frame flag
		}
		else {
		// packet too short if cbuf < &rxbuf[MINOCTETS]

		// put only one beginning flag into buffer
		rxbuf[0] = PKT_Flag;
		cbuf = &rxbuf[1];

		if (debug::level == debug::DEBUG_LEVEL)
			fprintf(stderr,"7e ");
		}
	}
	else if (seq_ones == 5) { // need bit un-stuffing
		c <<= 1;  // shift c back to skip stuffed bit
		--bcounter;
	}

	seq_ones = 0;
	}
	else {
	c |= (1 << (pkt_nbits-1)); // bits are sent lsb first
	++seq_ones;

	//if (seq_ones > 6) { // something is wrong
	//}
	}

	if (bcounter == pkt_nbits) {
	bcounter = 0;
	if (cbuf < &rxbuf[MAXOCTETS]) {
		*cbuf++ = c;

		if (debug::level == debug::DEBUG_LEVEL)
		fprintf(stderr,"%02x ",c);
	}
	else
		// else complain: cbuf is at MAXOCTETS
		LOG_WARN("Long input packet, %d octets!",(int)(cbuf - &rxbuf[0]));
	}

	/*
	//   when flag found: keep collecting flags (0x7e) until !(0x7e) found
	//   (first non-0x7e begins DATA)

	//   keep collecting bits in DATA, 8 at-a-time, until 0x7e found
	//   while collecting bits, perform bit-unstuffing so we place in
	//   databuffer exactly 8 bits at-a-time
	//   (at times more than 8 bits in to 8 bits out)
	//   first trailing 0x7e ends DATA and begins STOP
	*/
}

void pkt::Metric()
{
	double snr = 0;
	double pr = signal_power / noise_power;

	power_ratio = decayavg(power_ratio, pr, pr-power_ratio > 0 ? 2 : 8);
	snr = 10*log10( power_ratio );

	snprintf(msg2, sizeof(msg2), "s/n %3.0f dB", snr-snr_avg);
	put_Status2(msg2);

	metric = CLAMP(power_ratio, 0.0, 100.0);
	display_metric(metric);

	if (metric < 5.0)
	snr_avg = decayavg(snr_avg, snr, 8);
}

void pkt::idle_signal_power(double sample)
{
	// average of signal energy over PKT_IdleLen duration

	sample *= sample;

	idle_signal_pwr += sample;
	idle_signal_pwr -= idle_signal_buf[idle_signal_buf_ptr];
	idle_signal_buf[idle_signal_buf_ptr] = sample;

	++idle_signal_buf_ptr %= pkt_idlelen; // circular buffer
}

#ifndef NDEBUG
double pkt::corr_power(cmplx z)
#else
inline double pkt::corr_power(cmplx z)
#endif
{
	// scaled magnitude
	double power = norm(z);
	power /= 2 * symbollen;

	return power;
}

void pkt::correlate(double sample)
{
	cmplx yl, yh, yt;

	yl = nco_lo->cmplx_sample();
	yl *= sample;

	lo_signal_energy += yl;
	lo_signal_energy -= lo_signal_buf[correlate_buf_ptr];
	lo_signal_buf[correlate_buf_ptr] = yl;
	lo_signal_corr = lo_signal_gain * corr_power(lo_signal_energy);

	yh = nco_hi->cmplx_sample();
	yh *= sample;

	hi_signal_energy += yh;
	hi_signal_energy -= hi_signal_buf[correlate_buf_ptr];
	hi_signal_buf[correlate_buf_ptr] = yh;
	hi_signal_corr = hi_signal_gain * corr_power(hi_signal_energy);

	yt = nco_mid->cmplx_sample();
	yt *= sample;

	mid_signal_energy += yt;
	mid_signal_energy -= mid_signal_buf[correlate_buf_ptr];
	mid_signal_buf[correlate_buf_ptr] = yt;
	//mid_signal_corr = corr_power(mid_signal_energy);

	++correlate_buf_ptr %= symbollen; // SymbolLen correlation window

	yt = mid_signal_energy;

	if (abs(lo_signal_corr - hi_signal_corr) < 0.2) { // mid-symbol or noise
	pipe[pipeptr] = 0.0;

	QI[QIptr] = cmplx(yt.imag(), yt.real());
	}
	else if (lo_signal_corr > hi_signal_corr) {
	pipe[pipeptr] = 0.5;

	QI[QIptr] = cmplx (0.125 * yt.imag(), yt.real());
	}
	else {
	pipe[pipeptr] = -0.5;

	QI[QIptr] = cmplx( yt.real(), 0.125 * yt.imag());
	}
	++pipeptr %= pkt_syncdisplen;

	yt_avg = decayavg(yt_avg, abs(yt), symbollen/2);

	if (yt_avg > 0)  QI[QIptr] = QI[QIptr] / yt_avg;

	++QIptr %= MAX_ZLEN;
}

void pkt::detect_signal()
{
	double sig_corr = lo_signal_corr + hi_signal_corr;// - mid_signal_corr;

	signal_pwr += sig_corr;
	signal_pwr -= signal_buf[signal_buf_ptr];
	signal_buf[signal_buf_ptr] = sig_corr;

	++signal_buf_ptr %= pkt_detectlen; // circular buffer

	signal_power = signal_pwr / pkt_detectlen * signal_gain;
	noise_power = idle_signal_pwr / pkt_idlelen;

	// allow lazy eval
	if (signal_power > PKT_MinSignalPwr && signal_power > noise_power) {
	// lo+hi freq signals are present
	detect_drop = pkt_detectlen; // reset signal drop counter

	if (rxstate == PKT_RX_STATE_DROP) {
		rxstate = PKT_RX_STATE_START;

		// init STATE_START
		signal_gain = 1.0; // 5.0
		scounter = pkt_detectlen; //(was) 9 * symbollen;//11 instead of 9?
		lo_sync = 100.0;
		hi_sync = -100.0;
	}
	}
	else if (--detect_drop < 1 && rxstate == PKT_RX_STATE_DATA) { // lazy eval
	// give up on signals - gone,gone.
	rxstate = PKT_RX_STATE_STOP;
	scounter = 0; // (scounter is signed int)

	if (debug::level == debug::DEBUG_LEVEL)
		fprintf(stderr,"\n");
	}
}

void pkt::do_sync()
{
	double power_delta = lo_signal_corr - hi_signal_corr;

	lo_sync_ptr--; hi_sync_ptr--;

	if (lo_sync > power_delta && power_delta < 0) {
	lo_sync_ptr = 0;
	lo_sync = power_delta;
	}

	if (hi_sync < power_delta && power_delta > 0) {
	hi_sync_ptr = 0;
	hi_sync = power_delta;
	}

	if (--scounter < 1) {
	int offset;

	if (fabs(hi_sync) > fabs(lo_sync))
		offset = -hi_sync_ptr;
	else
		offset = -lo_sync_ptr;

	offset %= symbollen; // clamp offset to +/- one symbol

	mid_symbol = symbollen - offset;
	prev_symbol = pll_symbol = hi_signal_corr < lo_signal_corr;
	
	rxstate = PKT_RX_STATE_DATA;
	seq_ones = 0;	 // reset once on transition to STATE_DATA
	cbuf = &rxbuf[0]; // and reset packet buffer ptr
	}
}

void pkt::rx_data()
{
	bool tbit = hi_signal_corr < lo_signal_corr; // detect current symbol

	mid_symbol -= 1;
	int imid_symbol = (int) floor(mid_symbol+0.5);

	// hard-decision symbol decoding.  done at mid-symbol (imid_symbol < 1).
	//
	// might do instead a soft-decision by cumulative voting
	// of more than one mid-symbol sample?  symbollen-2 samples max?
	//
	if (imid_symbol < 1) { // counting down to middle of next symbol

	bool bit = prev_symbol == tbit; // detect symbol change

	prev_symbol = pll_symbol = tbit;
	mid_symbol += symbollen; // advance to middle of next symbol

	// remember to check value of reverse here to determine symbol
	rx(reverse ? !bit : bit);
	}
	else
	if (tbit != pll_symbol) { // update PLL
		if (mid_symbol < ((double)symbollen/2))
		mid_symbol += PKT_PLLAdjVal;
		else
		mid_symbol -= PKT_PLLAdjVal;

		pll_symbol = tbit;
	}
}

int pkt::rx_process(const double *buf, int len)
{
	double x_n;

	if (select_val != progdefaults.PKT_BAUD_SELECT) {
	// need to re-init modem
		restart();
	}

	Metric();

	while (len-- > 0) {

		x_n = *buf++;

		switch (rxstate) {

		case PKT_RX_STATE_STOP:
		default:
		rxstate = PKT_RX_STATE_IDLE;
		// fallthrough with next sample (x_(n+1))

		case PKT_RX_STATE_IDLE:
			idle_signal_power( x_n );

			if (--scounter < 1) {
			rxstate = PKT_RX_STATE_DROP;

			scounter = pkt_idlelen;
			signal_gain = 1.0;

			signal_pwr = signal_buf_ptr = 0;

			for(int i = 0; i < pkt_detectlen; i++)
				signal_buf[i] = 0;

			lo_signal_energy = hi_signal_energy =
				mid_signal_energy = cmplx(0, 0);

			yt_avg = correlate_buf_ptr = 0;

			for(int i = 0; i < symbollen; i++)
				lo_signal_buf[i] = hi_signal_buf[i] =
				mid_signal_buf[i] = cmplx(0, 0);
			}
			break;

		case PKT_RX_STATE_DROP:
			idle_signal_power( x_n );
			correlate( x_n );
			detect_signal();
			break;

		case PKT_RX_STATE_START:
			correlate( x_n );
			do_sync();
			break;

		case PKT_RX_STATE_DATA:
			correlate( x_n );
			rx_data();
			detect_signal();
			break;
		}
	} // while(len)

	if (metric < progStatus.sldrSquelchValue && progStatus.sqlonoff) {
		if (clear_zdata) {
			for (int i = 0; i < MAX_ZLEN; i++) 
				QI[i] = cmplx(0,0);
			QIptr = 0;
			set_zdata(QI, MAX_ZLEN);
			clear_zdata = false;
			clear_syncscope();
		}
	} else {
		clear_zdata = true;
		update_syncscope();
		set_zdata(QI, MAX_ZLEN);
	}

	return 0;
}

//=====================================================================
// PKT1200 transmit
//=====================================================================

void pkt::send_symbol(bool bit)
{
	if (reverse)  bit = !bit;

	for (int i = 0; i < symbollen; i++) {
	if (bit)
		outbuf[i] = hi_tone->sample() * hi_txgain; // modem::outbuf[]
	else
		outbuf[i] = lo_tone->sample() * lo_txgain; // modem::outbuf[]
	}

	// adjust nco phase accumulator to pick up where the other nco left off
	if (bit)
	lo_tone->setphaseacc(hi_tone->getphaseacc());
	else
	hi_tone->setphaseacc(lo_tone->getphaseacc());

	ModulateXmtr(outbuf, symbollen); // ignore retv?
}

/*
void pkt::send_stop()
{
	double freq;
	bool invert = reverse;

	if (invert)
		freq = get_txfreq_woffset() - pkt_shift / 2.0;
	else
		freq = get_txfreq_woffset() + pkt_shift / 2.0;

	//	stoplen = 0;
	// stoplen ::= (int) ((1.0 * 12000 / 1200) + 0.5) = 10

	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 pkt::send_char(unsigned char c)
{
	// if nostuff == 1 then { do _no_ bit stuffing }
	// else { keep track of no-transition (one bits) and stuff as needed }

	for (int i = 0; i < pkt_nbits; i++) {

	if ((c & 0x01) == 0) {
		currbit = !currbit; // transition on zero bits
		send_symbol(currbit);

		nr_ones = 0;
	}
	else {
		send_symbol(currbit);

		if (++nr_ones == 5 && !nostuff) {
		currbit = !currbit; // stuff zero bit
		send_symbol(currbit);

		nr_ones = 0;
		}
	}

	c >>= 1; // lsb to msb bit order
	}
}

string upcase(string s)
{
	// this function is also defined in arq::upcase
	// how to make a fldigi::upcase and use it here and in flarq?
	// maybe add it to misc/strutil.cxx ?

	for (size_t i = 0; i < s.length(); i++)
	s[i] = toupper(s[i]);
	return s;
}

void pkt::send_msg(unsigned char c)
{
	// form TX pane incoming chars into a valid UI AX.25 packet
	// in KISS mode, we do not create a packet header.
	if (!did_pkt_head) {
	int i, j;
	// the following is split into two because putting it all together
	// as "upcase(progdefaults.myCall).c_str()" would compile but not
	// work correctly.  Callsign decoded as "******-9" (wrong!).
	string ts = upcase(progdefaults.myCall);
	const char *src = ts.c_str(), // MYCALL
		dest2[] = "WIDE2 "; // digi
	char dest[7];

	snprintf(dest, 7, "APL%1d%02d", FLDIGI_VERSION_MAJOR,
		 FLDIGI_VERSION_MINOR); // dest[] = "APL321"

	for (i = 7, j = 0; i < 13; i++, j++) { // put src callsign in frame
		if (src[j] == 0)  break;
		txbuf[i] = src[j] << 1;
	}
	while (j++ < 6) {
		txbuf[i++] = ' ' << 1; // padding
	}

	txbuf[13] = '9' << 1; // src + SSID ==> MYCALL-9

	for (i = 0, j = 0; i < 6; i++, j++ ) { // put dest callsign in frame
		txbuf[i] = dest[j] << 1;
	}
	txbuf[6] = '1' << 1; // dest[] + SSID ==> APL321-1

	if (pkt_baud >= 1200) {
		for (i = 14, j = 0; i < 20; i++, j++) { // put digi callsign in frame
		txbuf[i] = dest2[j] << 1;
		}
		txbuf[20] = '1' << 1; // dest2[] + SSID ==> WIDE1-1
		txbuf[20] |= 0x01; // add last callsign flag

		tx_cbuf = &txbuf[21];
	}
	else { // use no digi when baud < 1200. limited bandwidth!
		txbuf[6] |= 0x01; // add last callsign flag

		tx_cbuf = &txbuf[14];
	}

	*tx_cbuf++ = 0x03; // add CTRL val for U_FRAME type UI
	*tx_cbuf++ = 0xf0;

	unsigned char *tc = &txbuf[0];

	while (tc < tx_cbuf) {
		send_char(*tc++);
		tx_char_count--;  // must count header chars in pkt length
	}

	did_pkt_head = true;
	}

	send_char(c);
	*tx_cbuf++ = c;
}


int pkt::tx_process()
{
	if (pretone > 0) {
	while (pretone-- > 0) {
		send_symbol(0); // Mark (low) tone
	}
	}

	if (preamble > 0) {
	nostuff = 1;  // turn off bit stuffing here
	while (preamble-- > 0) {
		send_char(PKT_Flag);
	}
	nostuff = 0;  // send_char() is 8-bit clean
	tx_char_count--; // count only last flag char
	}

	static int c = 0;
	if (tx_char_count >  0)
	c = get_tx_char();

	// TX buffer empty
	//	if (c == 0x03 || stopflag) {
	if (c == GET_TX_CHAR_ETX || stopflag || tx_char_count == 0) {
	if (!stopflag) {
		// compute FCS and add it to the frame via *tx_cbuf
		unsigned int fcs = computeFCS(&txbuf[0], tx_cbuf);
		// (B == Byte, b == bit)
		unsigned char tc = (fcs & 0xFF00) >> 8; // msB

		*tx_cbuf++ = tc;
		send_char(tc);

		tc = (unsigned char)(fcs & 0x00FF); // lsB

		*tx_cbuf++ = tc;
		send_char(tc);

		nostuff = 1;  // turn off bit stuffing
		while (postamble-- > 0) {
		send_char(PKT_Flag);
		}
		nostuff = 0;

		stopflag = true;
		return 0;
	}

	stopflag = false;
	tx_char_count = MAXOCTETS-3; // leave room for FCS and end-flag
	tx_cbuf = &txbuf[0];
	currbit = 0;
	did_pkt_head = false;

	cwid();
	return -1;
	}

	if (tx_char_count-- > 0)
	send_msg((unsigned char)c);
	else {
	// else complain:  maybe auto-segment?
	LOG_WARN("Long output packet, %d octets!",(int)(tx_cbuf - &txbuf[0]));

	return -1;
	}

	return 0;
}
