// ----------------------------------------------------------------------------
//
//	rsid.cxx
//
// Copyright (C) 2008-2010
//		Dave Freese, W1HKJ
// Copyright (C) 2009
//		Stelios Bounanos, M0GLD
//
// 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 <cmath>
#include <cstring>
#include <float.h>
#include <samplerate.h>

#include "rsid.h"
#include "filters.h"
#include "fft.h"
#include "misc.h"
#include "trx.h"
#include "fl_digi.h"
#include "configuration.h"
#include "confdialog.h"
#include "qrunner.h"
#include "notify.h"
#include "debug.h"

#include "main.h"
#include "arq_io.h"

LOG_FILE_SOURCE(debug::LOG_MODEM);

// Syntax: ELEM_(rsid_code, rsid_tag, fldigi_mode)
// fldigi_mode is NUM_MODES if mode is not available in fldigi,
// otherwise one of the tags defined in globals.h.
// rsid_tag is stringified and may be shown to the user.
#undef ELEM_
#define RSID_LIST                                       \
        ELEM_(1, BPSK31, MODE_PSK31)                    \
        ELEM_(110, QPSK31, MODE_QPSK31)                 \
        ELEM_(2, BPSK63, MODE_PSK63)                    \
        ELEM_(3, QPSK63, MODE_QPSK63)                   \
        ELEM_(4, BPSK125, MODE_PSK125)                  \
        ELEM_(5, QPSK125, MODE_QPSK125)                 \
        ELEM_(126, BPSK250, MODE_PSK250)                \
        ELEM_(127, QPSK250, MODE_QPSK250)               \
        ELEM_(173, BPSK500, MODE_PSK500)                \
                                                        \
        ELEM_(183, PSK125R, MODE_PSK125R)               \
        ELEM_(186, PSK250R, MODE_PSK250R)               \
        ELEM_(187, PSK500R, MODE_PSK500R)               \
                                                        \
        ELEM_(7, PSKFEC31, NUM_MODES)                   \
        ELEM_(8, PSK10, NUM_MODES)                      \
                                                        \
        ELEM_(9, MT63_500_LG, MODE_MT63_500)            \
        ELEM_(10, MT63_500_ST, MODE_MT63_500)           \
        ELEM_(11, MT63_500_VST, MODE_MT63_500)          \
        ELEM_(12, MT63_1000_LG, MODE_MT63_1000)         \
        ELEM_(13, MT63_1000_ST, MODE_MT63_1000)         \
        ELEM_(14, MT63_1000_VST, MODE_MT63_1000)        \
        ELEM_(15, MT63_2000_LG, MODE_MT63_2000)         \
        ELEM_(17, MT63_2000_ST, MODE_MT63_2000)         \
        ELEM_(18, MT63_2000_VST, MODE_MT63_2000)        \
                                                        \
        ELEM_(19, PSKAM10, NUM_MODES)                   \
        ELEM_(20, PSKAM31, NUM_MODES)                   \
        ELEM_(21, PSKAM50, NUM_MODES)                   \
                                                        \
        ELEM_(22, PSK63F, MODE_PSK63F)                  \
        ELEM_(23, PSK220F, NUM_MODES)                   \
                                                        \
        ELEM_(24, CHIP64, NUM_MODES)                    \
        ELEM_(25, CHIP128, NUM_MODES)                   \
                                                        \
        ELEM_(26, CW, MODE_CW)                          \
                                                        \
        ELEM_(27, CCW_OOK_12, NUM_MODES)                \
        ELEM_(28, CCW_OOK_24, NUM_MODES)                \
        ELEM_(29, CCW_OOK_48, NUM_MODES)                \
        ELEM_(30, CCW_FSK_12, NUM_MODES)                \
        ELEM_(31, CCW_FSK_24, NUM_MODES)                \
        ELEM_(33, CCW_FSK_48, NUM_MODES)                \
                                                        \
        ELEM_(34, PACTOR1_FEC, NUM_MODES)               \
                                                        \
        ELEM_(113, PACKET_110, NUM_MODES)               \
        ELEM_(35, PACKET_300, NUM_MODES)                \
        ELEM_(36, PACKET_1200, NUM_MODES)               \
                                                        \
        ELEM_(37, RTTY_ASCII_7, MODE_RTTY)              \
        ELEM_(38, RTTY_ASCII_8, MODE_RTTY)              \
        ELEM_(39, RTTY_45, MODE_RTTY)                   \
        ELEM_(40, RTTY_50, MODE_RTTY)                   \
        ELEM_(41, RTTY_75, MODE_RTTY)                   \
                                                        \
        ELEM_(42, AMTOR_FEC, NUM_MODES)                 \
                                                        \
        ELEM_(43, THROB_1, MODE_THROB1)                 \
        ELEM_(44, THROB_2, MODE_THROB2)                 \
        ELEM_(45, THROB_4, MODE_THROB4)                 \
        ELEM_(46, THROBX_1, MODE_THROBX1)               \
        ELEM_(47, THROBX_2, MODE_THROBX2)               \
        ELEM_(146, THROBX_4, MODE_THROBX4)              \
                                                        \
        ELEM_(204, CONTESTIA_4_125, MODE_CONTESTIA)     \
        ELEM_(55,  CONTESTIA_4_250, MODE_CONTESTIA)     \
        ELEM_(54,  CONTESTIA_4_500, MODE_CONTESTIA)     \
        ELEM_(255, CONTESTIA_4_1000, MODE_CONTESTIA)    \
        ELEM_(254, CONTESTIA_4_2000, MODE_CONTESTIA)    \
                                                        \
        ELEM_(169, CONTESTIA_8_125, MODE_CONTESTIA)     \
        ELEM_(49,  CONTESTIA_8_250, MODE_CONTESTIA)     \
        ELEM_(52,  CONTESTIA_8_500, MODE_CONTESTIA)     \
        ELEM_(117, CONTESTIA_8_1000, MODE_CONTESTIA)    \
        ELEM_(247, CONTESTIA_8_2000, MODE_CONTESTIA)    \
                                                        \
        ELEM_(50,  CONTESTIA_16_500, MODE_CONTESTIA)    \
        ELEM_(53,  CONTESTIA_16_1000, MODE_CONTESTIA)   \
        ELEM_(259, CONTESTIA_16_2000, MODE_CONTESTIA)   \
                                                        \
        ELEM_(51,  CONTESTIA_32_1000, MODE_CONTESTIA)   \
        ELEM_(201, CONTESTIA_32_2000, MODE_CONTESTIA)   \
                                                        \
        ELEM_(194, CONTESTIA_64_500, MODE_CONTESTIA)    \
        ELEM_(193, CONTESTIA_64_1000, MODE_CONTESTIA)   \
        ELEM_(191, CONTESTIA_64_2000, MODE_CONTESTIA)   \
                                                        \
        ELEM_(261, CONTESTIA_128_2000, MODE_CONTESTIA)  \
                                                        \
        ELEM_(56, VOICE, NUM_MODES)                     \
                                                        \
        ELEM_(60, MFSK8, MODE_MFSK8)                    \
        ELEM_(57, MFSK16, MODE_MFSK16)                  \
        ELEM_(147, MFSK32, MODE_MFSK32)                 \
        ELEM_(148, MFSK11, MODE_MFSK11)                 \
        ELEM_(152, MFSK22, MODE_MFSK22)                 \
                                                        \
        ELEM_(61, RTTYM_8_250, NUM_MODES)               \
        ELEM_(62, RTTYM_16_500, NUM_MODES)              \
        ELEM_(63, RTTYM_32_1000, NUM_MODES)             \
        ELEM_(65, RTTYM_8_500, NUM_MODES)               \
        ELEM_(66, RTTYM_16_1000, NUM_MODES)             \
        ELEM_(67, RTTYM_4_500, NUM_MODES)               \
        ELEM_(68, RTTYM_4_250, NUM_MODES)               \
        ELEM_(119, RTTYM_8_1000, NUM_MODES)             \
        ELEM_(170, RTTYM_8_125, NUM_MODES)              \
                                                        \
        ELEM_(203, OLIVIA_4_125, MODE_OLIVIA)           \
        ELEM_(75,  OLIVIA_4_250, MODE_OLIVIA)           \
        ELEM_(74,  OLIVIA_4_500, MODE_OLIVIA)           \
        ELEM_(229, OLIVIA_4_1000, MODE_OLIVIA)          \
        ELEM_(238, OLIVIA_4_2000, MODE_OLIVIA)          \
                                                        \
        ELEM_(163, OLIVIA_8_125, MODE_OLIVIA)           \
        ELEM_(69,  OLIVIA_8_250, MODE_OLIVIA)           \
        ELEM_(72,  OLIVIA_8_500, MODE_OLIVIA)           \
        ELEM_(116, OLIVIA_8_1000, MODE_OLIVIA)          \
        ELEM_(214, OLIVIA_8_2000, MODE_OLIVIA)          \
                                                        \
        ELEM_(70,  OLIVIA_16_500, MODE_OLIVIA)          \
        ELEM_(73,  OLIVIA_16_1000, MODE_OLIVIA)         \
        ELEM_(234, OLIVIA_16_2000, MODE_OLIVIA)         \
                                                        \
        ELEM_(71,  OLIVIA_32_1000, MODE_OLIVIA)         \
        ELEM_(221, OLIVIA_32_2000, MODE_OLIVIA)         \
                                                        \
        ELEM_(211, OLIVIA_64_2000, MODE_OLIVIA)         \
                                                        \
        ELEM_(76, PAX, NUM_MODES)                       \
        ELEM_(77, PAX2, NUM_MODES)                      \
        ELEM_(78, DOMINOF, NUM_MODES)                   \
        ELEM_(79, FAX, NUM_MODES)                       \
        ELEM_(81, SSTV, NUM_MODES)                      \
                                                        \
        ELEM_(84, DOMINOEX_4, MODE_DOMINOEX4)           \
        ELEM_(85, DOMINOEX_5, MODE_DOMINOEX5)           \
        ELEM_(86, DOMINOEX_8, MODE_DOMINOEX8)           \
        ELEM_(87, DOMINOEX_11, MODE_DOMINOEX11)         \
        ELEM_(88, DOMINOEX_16, MODE_DOMINOEX16)         \
        ELEM_(90, DOMINOEX_22, MODE_DOMINOEX22)         \
        ELEM_(92, DOMINOEX_4_FEC, MODE_DOMINOEX4)       \
        ELEM_(93, DOMINOEX_5_FEC, MODE_DOMINOEX5)       \
        ELEM_(97, DOMINOEX_8_FEC, MODE_DOMINOEX8)       \
        ELEM_(98, DOMINOEX_11_FEC, MODE_DOMINOEX11)     \
        ELEM_(99, DOMINOEX_16_FEC, MODE_DOMINOEX16)     \
        ELEM_(101, DOMINOEX_22_FEC, MODE_DOMINOEX22)    \
                                                        \
        ELEM_(104, FELD_HELL, MODE_FELDHELL)            \
        ELEM_(105, PSK_HELL, NUM_MODES)                 \
        ELEM_(106, HELL_80, MODE_HELL80)                \
        ELEM_(107, FM_HELL_105, MODE_FSKH105)           \
        ELEM_(108, FM_HELL_245, NUM_MODES)              \
                                                        \
        ELEM_(114, MODE_141A, NUM_MODES)                \
        ELEM_(123, DTMF, NUM_MODES)                     \
        ELEM_(125, ALE400, NUM_MODES)                   \
        ELEM_(131, FDMDV, NUM_MODES)                    \
                                                        \
        ELEM_(132, JT65_A, NUM_MODES)                   \
        ELEM_(134, JT65_B, NUM_MODES)                   \
        ELEM_(135, JT65_C, NUM_MODES)                   \
                                                        \
        ELEM_(136, THOR_4, MODE_THOR4)                  \
        ELEM_(137, THOR_8, MODE_THOR8)                  \
        ELEM_(138, THOR_16, MODE_THOR16)                \
        ELEM_(139, THOR_5, MODE_THOR5)                  \
        ELEM_(143, THOR_11, MODE_THOR11)                \
        ELEM_(145, THOR_22, MODE_THOR22)                \
                                                        \
        ELEM_(153, CALL_ID, NUM_MODES)                  \
                                                        \
        ELEM_(155, PACKET_PSK1200, NUM_MODES)           \
        ELEM_(156, PACKET_PSK250, NUM_MODES)            \
        ELEM_(159, PACKET_PSK63, NUM_MODES)             \
                                                        \
        ELEM_(172, MODE_188_110A_8N1, NUM_MODES)        \
                                                        \
        /* NONE must be the last element */             \
        ELEM_(0, NONE, NUM_MODES)

#define ELEM_(code_, tag_, mode_) RSID_ ## tag_ = code_,
enum { RSID_LIST };
#undef ELEM_

#define ELEM_(code_, tag_, mode_) { RSID_ ## tag_, mode_, #tag_ },
const RSIDs cRsId::rsid_ids[] = { RSID_LIST };
#undef ELEM_
const int cRsId::rsid_ids_size = sizeof(rsid_ids)/sizeof(*rsid_ids) - 1;

const int cRsId::Squares[] = {
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
	0, 2, 4, 6, 8,10,12,14, 9,11,13,15, 1, 3, 5, 7,
	0, 3, 6, 5,12,15,10, 9, 1, 2, 7, 4,13,14,11, 8,
	0, 4, 8,12, 9,13, 1, 5,11,15, 3, 7, 2, 6,10,14,
	0, 5,10,15,13, 8, 7, 2, 3, 6, 9,12,14,11, 4, 1,
	0, 6,12,10, 1, 7,13,11, 2, 4,14, 8, 3, 5,15, 9,
	0, 7,14, 9, 5, 2,11,12,10,13, 4, 3,15, 8, 1, 6,
	0, 8, 9, 1,11, 3, 2,10,15, 7, 6,14, 4,12,13, 5,
	0, 9,11, 2,15, 6, 4,13, 7,14,12, 5, 8, 1, 3,10,
	0,10,13, 7, 3, 9,14, 4, 6,12,11, 1, 5,15, 8, 2,
	0,11,15, 4, 7,12, 8, 3,14, 5, 1,10, 9, 2, 6,13,
	0,12, 1,13, 2,14, 3,15, 4, 8, 5, 9, 6,10, 7,11,
	0,13, 3,14, 6,11, 5, 8,12, 1,15, 2,10, 7, 9, 4,
	0,14, 5,11,10, 4,15, 1,13, 3, 8, 6, 7, 9, 2,12,
	0,15, 7, 8,14, 1, 9, 6, 5,10, 2,13,11, 4,12, 3
};

const int cRsId::indices[] = {
	2, 4, 8, 9, 11, 15, 7, 14, 5, 10, 13, 3
};

cRsId::cRsId()
{
	int error;
	src_state = src_new(progdefaults.sample_converter, 1, &error);
	if (error) {
		LOG_ERROR("src_new error %d: %s", error, src_strerror(error));
		abort();
	}
	src_data.end_of_input = 0;

	reset();

	rsfft = new Cfft(RSID_FFT_SIZE);

	memset(aHashTable1, 255, sizeof(aHashTable1));
	memset(aHashTable2, 255, sizeof(aHashTable2));
	memset(fftwindow, 0, RSID_ARRAY_SIZE * sizeof(double));
//	BlackmanWindow(fftwindow, RSID_FFT_SIZE);
//	HammingWindow(fftwindow, RSID_FFT_SIZE);
//	HanningWindow(fftwindow, RSID_FFT_SIZE);
	RectWindow(fftwindow, RSID_FFT_SIZE);
	
	pCodes = new unsigned char[rsid_ids_size * RSID_NSYMBOLS];
	memset(pCodes, 0, rsid_ids_size * RSID_NSYMBOLS);

	// Initialization  of assigned mode/submode IDs.
	// HashTable is used for finding a code with lowest Hamming distance.
	unsigned char* c;
	int hash1, hash2;
	for (int i = 0; i < rsid_ids_size; i++) {
		c = pCodes + i * RSID_NSYMBOLS;
		Encode(rsid_ids[i].rs, c);
		hash1 = c[11] | (c[12] << 4);
		hash2 = c[13] | (c[14] << 4);
		aHashTable1[hash1] = i;
		aHashTable2[hash2] = i;
	}

	nBinLow = RSID_RESOL + 1;
	nBinHigh = RSID_FFT_SIZE - 32;

	outbuf = 0;
	symlen = 0;
}

cRsId::~cRsId()
{
	delete [] pCodes;
	delete [] outbuf;
	src_delete(src_state);
}

void cRsId::reset()
{
	iPrevDistance = 99;
	bPrevTimeSliceValid = false;
	iTime = 0;
	memset(aInputSamples, 0, sizeof(aInputSamples));
	memset(aFFTReal, 0, sizeof(aFFTReal));
	memset(aFFTAmpl, 0, sizeof(aFFTAmpl));
	memset(aBuckets, 0, sizeof(aBuckets));

	int error = src_reset(src_state);
	if (error)
		LOG_ERROR("src_reset error %d: %s", error, src_strerror(error));
	src_data.src_ratio = 0.0;
	inptr = aInputSamples + RSID_FFT_SAMPLES;
}

void cRsId::Encode(int code, unsigned char *rsid)
{
	rsid[0] = code >> 8;
	rsid[1] = (code >> 4) & 0x0f;
	rsid[2] = code & 0x0f;
	for (int i = 3; i < RSID_NSYMBOLS; i++)
		rsid[i] = 0;
	for (int i = 0; i < 12; i++) {
		for (int j = RSID_NSYMBOLS - 1; j > 0; j--)
			rsid[j] = rsid[j - 1] ^ Squares[(rsid[j] << 4) + indices[i]];
		rsid[0] = Squares[(rsid[0] << 4) + indices[i]];
	}
}


void cRsId::CalculateBuckets(const double *pSpectrum, int iBegin, int iEnd)
{
	double Amp = 0.0, AmpMax = 0.0;
	int iBucketMax = iBegin - RSID_RESOL;
	int j;

	for (int i = iBegin; i < iEnd; i += RSID_RESOL) {
		if (iBucketMax == i - RSID_RESOL) {
			AmpMax = pSpectrum[i];
			iBucketMax = i;
			for (j = i + RSID_RESOL; j < i + RSID_NTIMES + RSID_RESOL; j += RSID_RESOL) {
				Amp = pSpectrum[j];
				if (Amp > AmpMax) {
					AmpMax = Amp;
					iBucketMax = j;
				}
			}
		}
		else {
			j = i + RSID_NTIMES;
			Amp = pSpectrum[j];
			if (Amp > AmpMax) {
				AmpMax    = Amp;
				iBucketMax = j;
			}
		}
		aBuckets[iTime][i] = (iBucketMax - i) >> 1;
	}
}

void cRsId::receive(const float* buf, size_t len)
{
	double src_ratio = RSID_SAMPLE_RATE / active_modem->get_samplerate();
	bool resample = (fabs(src_ratio - 1.0) >= DBL_EPSILON);
	size_t ns;

	while (len) {
		ns = inptr - aInputSamples;
		if (ns >= RSID_FFT_SAMPLES) // inptr points to second half of aInputSamples
			ns -= RSID_FFT_SAMPLES;
		ns = RSID_FFT_SAMPLES - ns; // number of additional samples we need to call search()

		if (resample) {
			if (src_data.src_ratio != src_ratio)
				src_set_ratio(src_state, src_data.src_ratio = src_ratio);
			src_data.data_in = const_cast<float*>(buf);
			src_data.input_frames = len;
			src_data.data_out = inptr;
			src_data.output_frames = ns;
			src_data.input_frames_used = 0;
			int error = src_process(src_state, &src_data);
			if (unlikely(error)) {
				LOG_ERROR("src_process error %d: %s", error, src_strerror(error));
				return;
			}
			inptr += src_data.output_frames_gen;
			buf += src_data.input_frames_used;
			len -= src_data.input_frames_used;
		}
		else {

			ns = MIN(ns, len);
			memcpy(inptr, buf, ns * sizeof(*inptr));
			inptr += ns;
			buf += ns;
			len -= ns;
		}

		ns = inptr - aInputSamples;
		if (ns == RSID_FFT_SAMPLES || ns == RSID_FFT_SIZE)
			search(); // will reset inptr if at end of input
	}
}

void cRsId::search(void)
{
	if (progdefaults.rsidWideSearch) {
		nBinLow = RSID_RESOL + 1;
		nBinHigh = RSID_FFT_SIZE - 32;
	}
	else {
		double centerfreq = active_modem->get_freq();
		nBinLow = (int)((centerfreq  - 100.0 * RSID_RESOL) * 2048.0 / RSID_SAMPLE_RATE);
		if (nBinLow < RSID_RESOL + 1) nBinLow = RSID_RESOL + 1;
		nBinHigh = (int)((centerfreq  + 100.0 * RSID_RESOL) * 2048.0 / RSID_SAMPLE_RATE);
		if (nBinHigh > RSID_FFT_SIZE -32) nBinHigh = RSID_FFT_SIZE;
	}

	bool bReverse = !(wf->Reverse() ^ wf->USB());
	if (bReverse) {
		nBinLow  = RSID_FFT_SIZE - nBinHigh;
		nBinHigh = RSID_FFT_SIZE - nBinLow;
	}

	if (inptr == aInputSamples + RSID_FFT_SIZE) {
		for (int i = 0; i < RSID_FFT_SIZE; i++)
			aFFTReal[i] = aInputSamples[i];
		inptr = aInputSamples;
	}
	else { // second half of aInputSamples is older
		for (size_t i = RSID_FFT_SAMPLES; i < RSID_FFT_SIZE; i++)
			aFFTReal[i - RSID_FFT_SAMPLES] = aInputSamples[i];
		for (size_t i = 0; i < RSID_FFT_SAMPLES; i++)
			aFFTReal[i + RSID_FFT_SAMPLES] = aInputSamples[i];
	}

	for (int i = 0; i < RSID_FFT_SIZE; i++) aFFTReal[i] *= fftwindow[i];
	memset(aFFTReal + RSID_FFT_SIZE, 0, RSID_FFT_SIZE * sizeof(double));

	rsfft->rdft(aFFTReal);

	memset(aFFTAmpl, 0, RSID_FFT_SIZE * sizeof(double));
	double Real, Imag;
	for (int i = 0; i < RSID_FFT_SIZE; i++) {
		Real = aFFTReal[2*i];
		Imag = aFFTReal[2*i + 1];
		if (unlikely(bReverse))
			aFFTAmpl[RSID_FFT_SIZE - 1 - i] = Real * Real + Imag * Imag;
		else
			aFFTAmpl[i] = Real * Real + Imag * Imag;
	}

	int SymbolOut = -1, BinOut = -1;
	if (search_amp(SymbolOut, BinOut)) {
		if (bReverse)
			BinOut = 1024 - BinOut - 31;
		apply(SymbolOut, BinOut);
	}
}

void cRsId::apply(int iSymbol, int iBin)
{
	ENSURE_THREAD(TRX_TID);

	double freq = (iBin + (RSID_NSYMBOLS - 1) * RSID_RESOL / 2) * RSID_SAMPLE_RATE / 2048.0;

	int n, mbin = NUM_MODES;
	for (n = 0; n < rsid_ids_size; n++) {
		if (rsid_ids[n].rs == iSymbol) {
			mbin = rsid_ids[n].mode;
			break;
		}
	}
	if (mbin == NUM_MODES) {
		char msg[50];
		if (n < rsid_ids_size) // RSID known but unimplemented
			snprintf(msg, sizeof(msg), "RSID: %s unimplemented", rsid_ids[n].name);
		else // RSID unknown; shouldn't  happen
			snprintf(msg, sizeof(msg), "RSID: code %d unknown", iSymbol);
		put_status(msg, 4.0);
		LOG_VERBOSE("%s", msg);
		return;
	}
	else if (!progdefaults.rsid_rx_modes.test(mbin)) {
		LOG_DEBUG("Ignoring RSID: %s @ %0.0f Hz", rsid_ids[n].name, freq);
		return;
	}
	else
		LOG_INFO("RSID: %s @ %0.0f Hz", rsid_ids[n].name, freq);

	if (!progdefaults.rsid_notify_only && progdefaults.rsid_auto_disable)
		REQ(toggleRSID);

	switch (iSymbol) {
 	case RSID_RTTY_ASCII_7:
		progdefaults.rtty_baud = 5;
		progdefaults.rtty_bits = 1;
		progdefaults.rtty_shift = 9;
		REQ(&set_rtty_tab_widgets);
		break;
	case RSID_RTTY_ASCII_8:
		progdefaults.rtty_baud = 5;
		progdefaults.rtty_bits = 2;
		progdefaults.rtty_shift = 9;
		REQ(&set_rtty_tab_widgets);
		break;
	case RSID_RTTY_45:
		progdefaults.rtty_baud = 1;
		progdefaults.rtty_bits = 0;
		progdefaults.rtty_shift = 3;
		REQ(&set_rtty_tab_widgets);
		break;
	case RSID_RTTY_50:
		progdefaults.rtty_baud = 2;
		progdefaults.rtty_bits = 0;
		progdefaults.rtty_shift = 3;
		REQ(&set_rtty_tab_widgets);
		break;
	case RSID_RTTY_75:
		progdefaults.rtty_baud = 4;
		progdefaults.rtty_bits = 0;
		progdefaults.rtty_shift = 9;
		REQ(&set_rtty_tab_widgets);
		break;
	// DominoEX / FEC
	case RSID_DOMINOEX_4: case RSID_DOMINOEX_5: case RSID_DOMINOEX_8:
	case RSID_DOMINOEX_11: case RSID_DOMINOEX_16: case RSID_DOMINOEX_22:
		progdefaults.DOMINOEX_FEC = false;
		REQ(&set_dominoex_tab_widgets);
		break;
	case RSID_DOMINOEX_4_FEC: case RSID_DOMINOEX_5_FEC: case RSID_DOMINOEX_8_FEC:
	case RSID_DOMINOEX_11_FEC: case RSID_DOMINOEX_16_FEC: case RSID_DOMINOEX_22_FEC:
		progdefaults.DOMINOEX_FEC = true;
		REQ(&set_dominoex_tab_widgets);
		break;
	// olivia parameters
	case RSID_OLIVIA_4_125:
		progdefaults.oliviatones = 1;
		progdefaults.oliviabw = 0;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_4_250:
		progdefaults.oliviatones = 1;
		progdefaults.oliviabw = 1;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_4_500:
		progdefaults.oliviatones = 1;
		progdefaults.oliviabw = 2;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_4_1000:
		progdefaults.oliviatones = 1;
		progdefaults.oliviabw = 3;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_4_2000:
		progdefaults.oliviatones = 1;
		progdefaults.oliviabw = 4;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_8_125:
		progdefaults.oliviatones = 2;
		progdefaults.oliviabw = 0;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_8_250:
		progdefaults.oliviatones = 2;
		progdefaults.oliviabw = 1;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_8_500:
		progdefaults.oliviatones = 2;
		progdefaults.oliviabw = 2;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_8_1000:
		progdefaults.oliviatones = 2;
		progdefaults.oliviabw = 3;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_8_2000:
		progdefaults.oliviatones = 2;
		progdefaults.oliviabw = 4;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_16_500:
		progdefaults.oliviatones = 3;
		progdefaults.oliviabw = 2;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_16_1000:
		progdefaults.oliviatones = 3;
		progdefaults.oliviabw = 3;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_16_2000:
		progdefaults.oliviatones = 3;
		progdefaults.oliviabw = 4;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_32_1000:
		progdefaults.oliviatones = 4;
		progdefaults.oliviabw = 3;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_32_2000:
		progdefaults.oliviatones = 4;
		progdefaults.oliviabw = 4;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_64_2000:
		progdefaults.oliviatones = 5;
		progdefaults.oliviabw = 4;
		REQ(&set_olivia_tab_widgets);
		break;
	// contestia parameters
	case RSID_CONTESTIA_4_125:
		progdefaults.contestiatones = 1;
		progdefaults.contestiabw = 0;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_4_250:
		progdefaults.contestiatones = 1;
		progdefaults.contestiabw = 1;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_4_500:
		progdefaults.contestiatones = 1;
		progdefaults.contestiabw = 2;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_4_1000:
		progdefaults.contestiatones = 1;
		progdefaults.contestiabw = 3;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_4_2000:
		progdefaults.contestiatones = 1;
		progdefaults.contestiabw = 4;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_8_125:
		progdefaults.contestiatones = 2;
		progdefaults.contestiabw = 0;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_8_250:
		progdefaults.contestiatones = 2;
		progdefaults.contestiabw = 1;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_8_500:
		progdefaults.contestiatones = 2;
		progdefaults.contestiabw = 2;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_8_1000:
		progdefaults.contestiatones = 2;
		progdefaults.contestiabw = 3;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_8_2000:
		progdefaults.contestiatones = 2;
		progdefaults.contestiabw = 4;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_16_500:
		progdefaults.contestiatones = 3;
		progdefaults.contestiabw = 2;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_16_1000:
		progdefaults.contestiatones = 3;
		progdefaults.contestiabw = 3;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_16_2000:
		progdefaults.contestiatones = 3;
		progdefaults.contestiabw = 4;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_32_1000:
		progdefaults.contestiatones = 4;
		progdefaults.contestiabw = 3;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_32_2000:
		progdefaults.contestiatones = 4;
		progdefaults.contestiabw = 4;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_64_500:
		progdefaults.contestiatones = 5;
		progdefaults.contestiabw = 2;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_64_1000:
		progdefaults.contestiatones = 5;
		progdefaults.contestiabw = 3;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_64_2000:
		progdefaults.contestiatones = 5;
		progdefaults.contestiabw = 4;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_128_2000:
		progdefaults.contestiatones = 6;
		progdefaults.contestiabw = 4;
		REQ(&set_contestia_tab_widgets);
		break;
	// mt63
	case RSID_MT63_500_LG: case RSID_MT63_1000_LG: case RSID_MT63_2000_LG:
		progdefaults.mt63_interleave = 64;
		break;
	case RSID_MT63_500_ST: case RSID_MT63_1000_ST: case RSID_MT63_2000_ST:
	case RSID_MT63_500_VST: case RSID_MT63_1000_VST: case RSID_MT63_2000_VST:
		progdefaults.mt63_interleave = 32;
		break;

	default:
		break;
	}

//	REQ(&configuration::loadDefaults, &progdefaults);

	if (mailclient || mailserver)
		REQ(pskmail_notify_rsid, mbin);

	if (progdefaults.rsid_mark && !progdefaults.rsid_notify_only) // mark current modem & freq
		REQ(note_qrg, false, "\nBefore RSID: ", "\n",
		    active_modem->get_mode(), 0LL, active_modem->get_freq());
	REQ(notify_rsid, mbin, freq);
	if (!progdefaults.rsid_notify_only) {
		if (progdefaults.rsid_squelch)
			REQ(init_modem_squelch, mbin, freq);
		else
			REQ(init_modem, mbin, freq);
	}
}

//=============================================================================
// search_amp routine #1
//=============================================================================

int cRsId::HammingDistance(int iBucket, unsigned char *p2)
{
	int dist = 0;
	int j = iTime - RSID_NTIMES + 1; // first value
	if (j < 0)
		j += RSID_NTIMES;
	for (int i = 0; i < RSID_NSYMBOLS; i++) {
		if (aBuckets[j][iBucket] != p2[i])//*p2++)
			if (++dist == 2)
				return dist;
		j += RSID_RESOL;//2;
		if (j >= RSID_NTIMES)
			j -= RSID_NTIMES;
	}
	return dist;
}

bool cRsId::search_amp( int &SymbolOut,	int &BinOut)
{
	int i, j;
	int iDistanceMin = 99;  // infinity
	int iDistance;
	int iBin		 = -1;
	int iSymbol		 = -1;
	int iEnd		 = nBinHigh - RSID_NTIMES;//30;
	int i1, i2, i3;

	if (++iTime == RSID_NTIMES)
		iTime = 0;

	i1 = iTime - 3 * RSID_RESOL;//6;
	i2 = i1 + RSID_RESOL;//2;
	i3 = i2 + RSID_RESOL;//2;

	if (i1 < 0) {
		i1 += RSID_NTIMES;
		if (i2 < 0) {
			i2 += RSID_NTIMES;
			if (i3 < 0)
				i3 += RSID_NTIMES;
		}
	}

	CalculateBuckets ( aFFTAmpl, nBinLow,     iEnd);//nBinHigh - 30);
	CalculateBuckets ( aFFTAmpl, nBinLow + 1, iEnd);//nBinHigh - 30);

	for (i = nBinLow; i < iEnd; ++ i) {
		j = aHashTable1[aBuckets[i1][i] | (aBuckets[i2][i] << 4)];
		if (j < rsid_ids_size)  { //!= 255) {
			iDistance = HammingDistance(i, pCodes + j * RSID_NSYMBOLS);
			if (iDistance < 2 && iDistance < iDistanceMin) {
				iDistanceMin = iDistance;
				iSymbol  	 = rsid_ids[j].rs;
				iBin		 = i;
			}
		}
		j = aHashTable2[aBuckets[i3][i] | (aBuckets[iTime][i] << 4)];
		if (j < rsid_ids_size)  { //!= 255) {
			iDistance = HammingDistance (i, pCodes + j * RSID_NSYMBOLS);
			if (iDistance < 2 && iDistance < iDistanceMin) {
				iDistanceMin = iDistance;
				iSymbol		 = rsid_ids[j].rs;
				iBin		 = i;
			}
		}
	}

	if (iSymbol == -1) {
		// No RSID found in this time slice.
		// If there is a code stored from the previous time slice, return it.
		if (bPrevTimeSliceValid) {
			SymbolOut			= iPrevSymbol;
			BinOut				= iPrevBin;
			DistanceOut	    	= iPrevDistance;
			MetricsOut			= 0;
			bPrevTimeSliceValid = false;
			return true;
		}
		return false;
	}

	if (! bPrevTimeSliceValid ||
		iDistanceMin <= iPrevDistance) {
		iPrevSymbol		= iSymbol;
		iPrevBin		= iBin;
		iPrevDistance	= iDistanceMin;
	}
	bPrevTimeSliceValid = true;
	return false;
}

//=============================================================================
// transmit rsid code for current mode
//=============================================================================

void cRsId::send(bool preRSID)
{
	trx_mode mode = active_modem->get_mode();

	if (!progdefaults.rsid_tx_modes.test(mode)) {
		LOG_DEBUG("Mode %s excluded, not sending RSID", mode_info[mode].sname);
		return;
	}

	if (!progdefaults.rsid_post && !preRSID)
		return;

	unsigned short rmode = RSID_NONE;

	switch (mode) {
	case MODE_RTTY :
		if (progdefaults.rtty_baud == 5 && progdefaults.rtty_bits == 1 && progdefaults.rtty_shift == 9)
			rmode = RSID_RTTY_ASCII_7;
		else if (progdefaults.rtty_baud == 5 && progdefaults.rtty_bits == 1 && progdefaults.rtty_shift == 9)
			rmode = RSID_RTTY_ASCII_8;
		else if (progdefaults.rtty_baud == 1 && progdefaults.rtty_bits == 0 && progdefaults.rtty_shift == 3)
			rmode = RSID_RTTY_45;
		else if (progdefaults.rtty_baud == 2 && progdefaults.rtty_bits == 0 && progdefaults.rtty_shift == 3)
			rmode = RSID_RTTY_50;
		else if (progdefaults.rtty_baud == 4 && progdefaults.rtty_bits == 0 && progdefaults.rtty_shift == 9)
			rmode = RSID_RTTY_75;
		else
			return;
		break;

	case MODE_OLIVIA:

		if (progdefaults.oliviatones == 1 && progdefaults.oliviabw == 0)
			rmode = RSID_OLIVIA_4_125;
		else if (progdefaults.oliviatones == 1 && progdefaults.oliviabw == 1)
			rmode = RSID_OLIVIA_4_250;
		else if (progdefaults.oliviatones == 1 && progdefaults.oliviabw == 2)
			rmode = RSID_OLIVIA_4_500;
		else if (progdefaults.oliviatones == 1 && progdefaults.oliviabw == 3)
			rmode = RSID_OLIVIA_4_1000;
		else if (progdefaults.oliviatones == 1 && progdefaults.oliviabw == 4)
			rmode = RSID_OLIVIA_4_2000;

		else if (progdefaults.oliviatones == 2 && progdefaults.oliviabw == 0)
			rmode = RSID_OLIVIA_8_125;
		else if (progdefaults.oliviatones == 2 && progdefaults.oliviabw == 1)
			rmode = RSID_OLIVIA_8_250;
		else if (progdefaults.oliviatones == 2 && progdefaults.oliviabw == 2)
			rmode = RSID_OLIVIA_8_500;
		else if (progdefaults.oliviatones == 2 && progdefaults.oliviabw == 3)
			rmode = RSID_OLIVIA_8_1000;
		else if (progdefaults.oliviatones == 2 && progdefaults.oliviabw == 4)
			rmode = RSID_OLIVIA_8_2000;

		else if (progdefaults.oliviatones == 3 && progdefaults.oliviabw == 2)
			rmode = RSID_OLIVIA_16_500;
		else if (progdefaults.oliviatones == 3 && progdefaults.oliviabw == 3)
			rmode = RSID_OLIVIA_16_1000;
		else if (progdefaults.oliviatones == 3 && progdefaults.oliviabw == 4)
			rmode = RSID_OLIVIA_16_2000;

		else if (progdefaults.oliviatones == 4 && progdefaults.oliviabw == 3)
			rmode = RSID_OLIVIA_32_1000;
		else if (progdefaults.oliviatones == 4 && progdefaults.oliviabw == 4)
			rmode = RSID_OLIVIA_32_2000;

		else if (progdefaults.oliviatones == 5 && progdefaults.oliviabw == 4)
			rmode = RSID_OLIVIA_64_2000;

		else
			return;
		break;

	case MODE_CONTESTIA:

		if (progdefaults.contestiatones == 1 && progdefaults.contestiabw == 0)
			rmode = RSID_CONTESTIA_4_125;
		else if (progdefaults.contestiatones == 1 && progdefaults.contestiabw == 1)
			rmode = RSID_CONTESTIA_4_250;
		else if (progdefaults.contestiatones == 1 && progdefaults.contestiabw == 2)
			rmode = RSID_CONTESTIA_4_500;
		else if (progdefaults.contestiatones == 1 && progdefaults.contestiabw == 3)
			rmode = RSID_CONTESTIA_4_1000;
		else if (progdefaults.contestiatones == 1 && progdefaults.contestiabw == 4)
			rmode = RSID_CONTESTIA_4_2000;

		else if (progdefaults.contestiatones == 2 && progdefaults.contestiabw == 0)
			rmode = RSID_CONTESTIA_8_125;
		else if (progdefaults.contestiatones == 2 && progdefaults.contestiabw == 1)
			rmode = RSID_CONTESTIA_8_250;
		else if (progdefaults.contestiatones == 2 && progdefaults.contestiabw == 2)
			rmode = RSID_CONTESTIA_8_500;
		else if (progdefaults.contestiatones == 2 && progdefaults.contestiabw == 3)
			rmode = RSID_CONTESTIA_8_1000;
		else if (progdefaults.contestiatones == 2 && progdefaults.contestiabw == 4)
			rmode = RSID_CONTESTIA_8_2000;

		else if (progdefaults.contestiatones == 3 && progdefaults.contestiabw == 2)
			rmode = RSID_CONTESTIA_16_500;
		else if (progdefaults.contestiatones == 3 && progdefaults.contestiabw == 3)
			rmode = RSID_CONTESTIA_16_1000;
		else if (progdefaults.contestiatones == 3 && progdefaults.contestiabw == 4)
			rmode = RSID_CONTESTIA_16_2000;

		else if (progdefaults.contestiatones == 4 && progdefaults.contestiabw == 3)
			rmode = RSID_CONTESTIA_32_1000;
		else if (progdefaults.contestiatones == 4 && progdefaults.contestiabw == 4)
			rmode = RSID_CONTESTIA_32_2000;

		else if (progdefaults.contestiatones == 5 && progdefaults.contestiabw == 2)
			rmode = RSID_CONTESTIA_64_500;
		else if (progdefaults.contestiatones == 5 && progdefaults.contestiabw == 3)
			rmode = RSID_CONTESTIA_64_1000;
		else if (progdefaults.contestiatones == 5 && progdefaults.contestiabw == 4)
			rmode = RSID_CONTESTIA_64_2000;

		else if (progdefaults.contestiatones == 6 && progdefaults.contestiabw == 4)
			rmode = RSID_CONTESTIA_128_2000;

		else
			return;
		break;

	case MODE_DOMINOEX4:
		if (progdefaults.DOMINOEX_FEC)
			rmode = RSID_DOMINOEX_4_FEC;
		break;
	case MODE_DOMINOEX5:
		if (progdefaults.DOMINOEX_FEC)
			rmode = RSID_DOMINOEX_5_FEC;
		break;
	case MODE_DOMINOEX8:
		if (progdefaults.DOMINOEX_FEC)
			rmode = RSID_DOMINOEX_8_FEC;
		break;
	case MODE_DOMINOEX11:
		if (progdefaults.DOMINOEX_FEC)
			rmode = RSID_DOMINOEX_11_FEC;
		break;
	case MODE_DOMINOEX16:
		if (progdefaults.DOMINOEX_FEC)
			rmode = RSID_DOMINOEX_16_FEC;
		break;
	case MODE_DOMINOEX22:
		if (progdefaults.DOMINOEX_FEC)
			rmode = RSID_DOMINOEX_22_FEC;
		break;

	case MODE_MT63_500:
		if (progdefaults.mt63_interleave == 32)
			rmode = RSID_MT63_500_ST;
		break;
	case MODE_MT63_1000:
		if (progdefaults.mt63_interleave == 32)
			rmode = RSID_MT63_1000_ST;
		break;
	case MODE_MT63_2000:
		if (progdefaults.mt63_interleave == 32)
			rmode = RSID_MT63_2000_ST;
		break;
	}

	// if rmode is still unset, look it up
	if (rmode == RSID_NONE) {
		for (size_t i = 0; i < sizeof(rsid_ids)/sizeof(*rsid_ids); i++) {
			if (mode == rsid_ids[i].mode) {
				rmode = rsid_ids[i].rs;
				break;
			}
		}
	}
	if (rmode == RSID_NONE)
		return;

	unsigned char rsid[RSID_NSYMBOLS];

	Encode(rmode, rsid);

	double sr = active_modem->get_samplerate();
	size_t len = (size_t)floor(RSID_SYMLEN * sr);
	if (unlikely(len != symlen)) {
		symlen = len;
		delete [] outbuf;
		outbuf = new double[symlen];
	}

/*
	// transmit 6 symbol periods of silence at end of transmission
	if (!preRSID) {
		memset(outbuf, 0, symlen * sizeof(*outbuf));
		for (int i = 0; i < 6; i++)
			active_modem->ModulateXmtr(outbuf, symlen);
	}
*/
	// transmit sequence of 15 symbols (tones)
	int iTone;
	double freq, phaseincr;
	double fr = 1.0 * active_modem->get_txfreq() - (RSID_SAMPLE_RATE * 7 / 1024);
	double phase = 0.0;


	for (int i = 0; i < 15; i++) {
		iTone = rsid[i];
		if (active_modem->get_reverse())
			iTone = 15 - iTone;
		freq = fr + iTone * RSID_SAMPLE_RATE / 1024;
		phaseincr = 2.0 * M_PI * freq / sr;

		for (size_t j = 0; j < symlen; j++) {
			phase += phaseincr;
			if (phase > 2.0 * M_PI) phase -= 2.0 * M_PI;
			outbuf[j] = sin(phase);
		}
		active_modem->ModulateXmtr(outbuf, symlen);

	}
// one symbol period of silence
	memset(outbuf, 0, symlen * sizeof(*outbuf));
	active_modem->ModulateXmtr(outbuf, symlen);
/*
	// transmit 6 symbol periods of silence at beginning of transmission
	if (preRSID) {
		memset(outbuf, 0, symlen * sizeof(*outbuf));
		for (int i = 0; i < 6; i++)
			active_modem->ModulateXmtr(outbuf, symlen);
	}
*/
}

