/*  -*-  mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; coding: utf-8  -*-  */
/************************************************************************************
 **                                                                                 **
 **                               mcHF QRP Transceiver                              **
 **                             K Atanassov - M0NKA 2014                            **
 **                                                                                 **
 **---------------------------------------------------------------------------------**
 **                                                                                 **
 **  File name:                                                                     **
 **  Description:                                                                   **
 **  Last Modified:                                                                 **
 **  Licence:		GNU GPLv3                                                          **
 ************************************************************************************/

// ----------------------------------------------------------------------------
// re-implemented from:
// https://www.wrbishop.com/ham/arduino-iambic-keyer-and-side-tone-generator/
// by Steven T. Elliott
// ----------------------------------------------------------------------------

// ----------------------------------------------------------------------------
//
// Major changes to improve CW edge smoothing
//
// 28.01.2017	hb9ewy
// - Blackman-Harris smoothing for minimal sidelobes
//   See: https://en.wikipedia.org/wiki/Window_function
// - CW_SMOOTH_TBL_SIZE=128 and cw_smooth_len=2 to remove spurious at +/- 3 kHz
// - Avoid clipping of smoothed edges and cw spike after signal
//   by improved timing
//
// Open issues
// - in internal keyer pause between dits and dots is too long
//   Not changes as issue had not been reported yet
// - Try interpolation with smoothing table to use less RAM
// ----------------------------------------------------------------------------

// Common
#include "assert.h"
#include "uhsdr_board.h"
#include "radio_management.h"
#include "softdds.h"
#include "cw_gen.h"
#include "cat_driver.h"
#include "ui_driver.h"
#include "uhsdr_digi_buffer.h"

// States
#define CW_IDLE             0
#define CW_DIT_CHECK        1
#define CW_DAH_CHECK        3
#define CW_KEY_DOWN         4
#define CW_KEY_UP           5
#define CW_PAUSE            6
#define CW_WAIT             7

#define CW_DIT_L            0x01
#define CW_DAH_L            0x02
#define CW_DIT_PROC         0x04
#define CW_END_PROC         0x10

//#define CW_SMOOTH_LEN       2	// with sm_table size of 128 -> 2 => ~5.3ms for edges, ~ 9 steps of 0.66 ms,
// 1 => 2.7ms , 5 steps of 0.66ms required.
// 3 => 8ms, 13 steps
//#define CW_SMOOTH_STEPS		9	// 1 step = 0.6ms; 13 for 8ms, 9 for 5.4 ms, for internal keyer


typedef struct PaddleState
{
	// State machine and port states
	uint32_t   port_state;
	uint32_t   cw_state;

	// Element duration
	int32_t   dit_time;
	int32_t   dah_time;
	int32_t   pause_time;
	int32_t   space_time;

	// Timers
	int32_t   key_timer;
	int32_t   break_timer;
	int32_t   space_timer;

	// Key clicks smoothing table current ptr
	uint32_t   sm_tbl_ptr;

	uint32_t   ultim;

	uint32_t   cw_char;
	uint32_t   sending_char;
} PaddleState;

// Public paddle state
PaddleState  ps;

static bool   CwGen_ProcessStraightKey( float32_t* i_buffer, float32_t* q_buffer, uint32_t size );
static bool   CwGen_ProcessIambic( float32_t* i_buffer, float32_t* q_buffer, uint32_t size );
static void   CwGen_TestFirstPaddle(void);
static void   CwGen_ResetBufferSending(void);

#define CW_SPACE_CHAR		1

// The vertical listing permits easier direct comparison of code vs. character in
// editors by placing both in vertically split window
const uint32_t cw_char_codes[] =
{
		CW_SPACE_CHAR,    // 		-> ' '
		2,    // . 		-> 'E'
		3,    // - 		-> 'T'
		10,   // .. 	-> 'I'
		11,   // .- 	-> 'A'
		14,   // -. 	-> 'N'
		15,   // -- 	-> 'M'
		42,   // ...	-> 'S'
		43,   // ..-	-> 'U'
		46,   // .-.	-> 'R'
		47,   // .--	-> 'W'
		58,	  // -..	-> 'D'
		59,   // -.-	-> 'K'
		62,   // --.	-> 'G'
		63,   // ---	-> 'O'
		170,  // ....	-> 'H'
		171,  // ...-	-> 'V'
		174,  // ..-.	-> 'F'
		186,  // .-..	-> 'L'
		190,  // .--.	-> 'P'
		191,  // .---	-> 'J'
		234,  // -...	-> 'B'
		235,  // -..-	-> 'X'
		238,  // -.-.	-> 'C'
		239,  // -.--	-> 'Y'
		250,  // --..	-> 'Z'
		251,  // --.-	-> 'Q'
		682,  // .....	-> '5'
		683,  // ....-	-> '4'
		687,  // ...--	-> '3'
		703,  // ..---  -> '2'
		767,  // .----  -> '1'
		938,  // -....  -> '6'
		939,  // -...-  -> '='
		942,  // -..-.  -> '/'
		1002, // --...  -> '7'
		1018, // ---..  -> '8'
		1022, // ----.  -> '9'
		1023, // -----  -> '0'
		2810, // ..--.. -> '?'
		2990, // .-..-. -> '_' / '"'
		3003, // .-.-.- -> '.'
		3054, // .--.-. -> '@'
		3070, // .----. -> '\''
		3755, // -....- -> '-'
		4015, // --..-- -> ','
		4074  // ---... -> ':'
};

#define CW_CHAR_CODES (sizeof(cw_char_codes)/sizeof(*cw_char_codes))
const char cw_char_chars[CW_CHAR_CODES] =
{
		' ',
		'E',
		'T',
		'I',
		'A',
		'N',
		'M',
		'S',
		'U',
		'R',
		'W',
		'D',
		'K',
		'G',
		'O',
		'H',
		'V',
		'F',
		'L',
		'P',
		'J',
		'B',
		'X',
		'C',
		'Y',
		'Z',
		'Q',
		'5',
		'4',
		'3',
		'2',
		'1',
		'6',
		'=',
		'/',
		'7',
		'8',
		'9',
		'0',
		'?',
		'"',
		'.',
		'@',
		'\'',
		'-',
		',',
		':'
};

const uint32_t cw_sign_codes[] =
{
		187, //   <AA>
		750, //   <AR>
		746, //   <AS>
		61114, // <CL>
		955, //   <CT>
		43690, // <HH>
		958, //   <KN>
		3775, //  <NJ>
		2747, //  <SK>
		686 //    <SN>
};

#define CW_SIGN_CODES (sizeof(cw_sign_codes)/sizeof(*cw_sign_codes))
const char* cw_sign_chars[CW_SIGN_CODES] =
{
		"AA",
		"AR",
		"AS",
		"CL",
		"CT",
		"HH",
		"KN",
		"NJ",
		"SK",
		"SN"
};

const char cw_sign_onechar[CW_SIGN_CODES] =
{
		'^', // AA
		'+', // AR
		'&', // AS
		'{', // CL
		'}', // CT
		0x7f, // HH
		'(', // KN
		'%', // NJ
		'>', // SK
		'~' // SN
};


// Blackman-Harris function to keep CW signal bandwidth narrow
#define CW_SMOOTH_TBL_SIZE  128
static const float sm_table[CW_SMOOTH_TBL_SIZE] =
{
		0.0,
		0.0000686004957883,
		0.0000945542356652,
		0.0001383179721175,
		0.0002006529396865,
		0.0002826248158738,
		0.0003856036471126,
		0.0005112637206964,
		0.0006615833583608,
		0.0008388446022225,
		0.0010456327590236,
		0.0012848357641460,
		0.0015596433227128,
		0.0018735457812732,
		0.0022303326801620,
		0.0026340909336119,
		0.0030892025821568,
		0.0036003420597745,
		0.0041724729166402,
		0.0048108439372987,
		0.0055209845935232,
		0.0063086997711494,
		0.0071800637107376,
		0.0081414131030473,
		0.0091993392820062,
		0.0103606794601087,
		0.0116325069539978,
		0.0130221203513468,
		0.0145370315740592,
		0.0161849527972270,
		0.0179737821882093,
		0.0199115884355984,
		0.0220065940436903,
		0.0242671573743564,
		0.0267017534248783,
		0.0293189533373318,
		0.0321274026424438,
		0.0351357982484650,
		0.0383528641934459,
		0.0417873261873527,
		0.0454478849786327,
		0.0493431885881256,
		0.0534818034615354,
		0.0578721846000008,
		0.0625226447365667,
		0.0674413226345205,
		0.0726361505915551,
		0.0781148212415210,
		0.0838847537530600,
		0.0899530595316440,
		0.0963265075384118,
		0.1030114893456630,
		0.1100139840548870,
		0.1173395232087210,
		0.1249931558332360,
		0.1329794137513460,
		0.1413022773119520,
		0.1499651416825930,
		0.1589707838558700,
		0.1683213305216760,
		0.1780182269583640,
		0.1880622070962730,
		0.1984532649066170,
		0.2091906272675180,
		0.2202727284569900,
		0.2316971864198910,
		0.2434607809523230,
		0.2555594339426260,
		0.2679881918029900,
		0.2807412102198870,
		0.2938117413448940,
		0.3071921235401840,
		0.3208737737849510,
		0.3348471828403530,
		0.3491019132612700,
		0.3636266003332790,
		0.3784089560027740,
		0.3934357758572420,
		0.4086929492012480,
		0.4241654722618500,
		0.4398374645449960,
		0.4556921883519000,
		0.4717120714516800,
		0.4878787328935640,
		0.5041730119288920,
		0.5205749999999980,
		0.5370640757398780,
		0.5536189429134430,
		0.5702176712181650,
		0.5868377398491150,
		0.6034560837207990,
		0.6200491422259460,
		0.6365929103995070,
		0.6530629923446170,
		0.6694346567663080,
		0.6856828944482830,
		0.7017824774982180,
		0.7177080201778180,
		0.7334340411253520,
		0.7489350267705930,
		0.7641854957350820,
		0.7791600640044710,
		0.7938335106543350,
		0.8081808439064380,
		0.8221773672888840,
		0.8357987456709900,
		0.8490210709420900,
		0.8618209271027980,
		0.8741754545375520,
		0.8860624132385800,
		0.8974602447536510,
		0.9083481326332570,
		0.9187060611570670,
		0.9285148721246550,
		0.9377563195016140,
		0.9464131217191720,
		0.9544690114333140,
		0.9619087825581750,
		0.9687183343980040,
		0.9748847127123550,
		0.9803961475602210,
		0.9852420877805500,
		0.9894132319790110,
		0.9929015559037870,
		0.9957003361066840,
		0.9978041697997700,
		0.9992089908321180,
		0.9999120817258750
};

void CwGen_SetSpeed()
{
	// 1800000 = 1.2s per dit == 1WPM ; 1500 impulse per second audio irq = 1800 ticks per 1 WPM dit
	// we scale with 100 for better precision and easier use of the weight value which is scaled by 100.
	// so 1800 * 100 = 180000

	// weight 1.00
//	int32_t dit_time         = 180000/ts.cw_keyer_speed + CW_SMOOTH_STEPS*100;  // +9 =  6ms * 1/1500 =  0,006*1500
	int32_t dit_time         = 180000/ts.cw_keyer_speed + ts.cw_smooth_steps * 100;  // +9 =  6ms * 1/1500 =  0,006*1500
//	int32_t dah_time         = 3*180000/ts.cw_keyer_speed + CW_SMOOTH_STEPS*100;  // +9 =  6ms * 1/1500 =  0,006*1500
	int32_t dah_time         = 3*180000/ts.cw_keyer_speed + ts.cw_smooth_steps * 100;  // +9 =  6ms * 1/1500 =  0,006*1500
//	int32_t pause_time       = 180000/ts.cw_keyer_speed - CW_SMOOTH_STEPS*100;  // -9 = -6ms * 1/1500 = -0,006*1500
	int32_t pause_time       = 180000/ts.cw_keyer_speed - ts.cw_smooth_steps * 100;  // -9 = -6ms * 1/1500 = -0,006*1500
	int32_t space_time       = 6*180000/ts.cw_keyer_speed;

	int32_t weight_corr = ((int32_t)ts.cw_keyer_weight-100) * dit_time/100;

	// we add the correction value to both dit and dah and subtract from pause. dah gets less change proportionally because of this
	ps.dit_time = (dit_time + weight_corr)/100;
	ps.dah_time = (dah_time + weight_corr)/100;
	ps.pause_time = (pause_time - weight_corr)/100;
	ps.space_time = space_time / 100;
}

static uint32_t CwGen_GetBreakTime( )
{
    return ts.cw_rx_delay*50 + 1;
}

static void CwGen_SetBreakTime(void)
{
	ps.break_timer = CwGen_GetBreakTime();      // break timer value
}

/**
 * @brief Initializes CW generator, called once during init phase
 */
void CwGen_Init(void)
{
    // Init Soft DDS
    float freq[2] = { 0.0, 0.0 };
    softdds_configRunIQ(freq,ts.samp_rate,0);

	CwGen_SetSpeed();

    /**
     * FIXME No point doing this check as the function is called during board init,
     * the rest of initialization would fail anyway if called anywhere else
     */
	if (ts.txrx_mode != TRX_MODE_TX  ||  ts.dmod_mode != DEMOD_CW)
	{
		// do not change if currently in CW transmit
		ps.cw_state         = CW_IDLE;
		CwGen_SetBreakTime();
		ps.key_timer		= 0;
	}

	ps.cw_char = 0;
	ps.space_timer = 0;
	ps.sending_char = 0;
}

/**
 * @brief remove clicks at start of tone
 */
static void CwGen_RemoveClickOnRisingEdge( float32_t* i_buffer, float32_t* q_buffer, uint32_t size )
{
    assert( i_buffer && q_buffer );
//	assert( !( size < CW_SMOOTH_LEN )); // size shouldn't be less
    assert( !( size < ts.cw_smooth_len )); // size shouldn't be less
	// Do not overload
	if(ps.sm_tbl_ptr < (CW_SMOOTH_TBL_SIZE ))
	{
		for(int i = 0, j = 0; i < size; i++)
		{
			i_buffer[i] = i_buffer[i] * sm_table[ps.sm_tbl_ptr];
			q_buffer[i] = q_buffer[i] * sm_table[ps.sm_tbl_ptr];

			j++;
//			if(j == CW_SMOOTH_LEN)
			if(j == ts.cw_smooth_len)
			{
				j = 0;

				ps.sm_tbl_ptr++;
				if(ps.sm_tbl_ptr > (CW_SMOOTH_TBL_SIZE - 1))
				{
					break;
					// leave loop and return
				}
			}
		}
	}
}

/**
 * @brief remove clicks at end of tone
 */
static void CwGen_RemoveClickOnFallingEdge( float32_t* i_buffer, float32_t* q_buffer, uint32_t size )
{
    assert( i_buffer && q_buffer );
//	assert( !( size < CW_SMOOTH_LEN )); // size shouldn't be less
    assert( !( size < ts.cw_smooth_len )); // size shouldn't be less
	// Do not overload
		// Fix ptr, so we start from the last element
    if(ps.sm_tbl_ptr > (CW_SMOOTH_TBL_SIZE - 1))
    {
        ps.sm_tbl_ptr = (CW_SMOOTH_TBL_SIZE - 1);
    }

    for( uint32_t i = 0,j = 0; i < size; i++) // iterate over the I&Q audio buffer elements
    {
        i_buffer[i] = i_buffer[i] * sm_table[ps.sm_tbl_ptr];
        q_buffer[i] = q_buffer[i] * sm_table[ps.sm_tbl_ptr];

        j++;
//		if(j == CW_SMOOTH_LEN)
        if(j == ts.cw_smooth_len)
        {
            j = 0;

            if(ps.sm_tbl_ptr > 0) // keep value @ ptr=0 to prevent trailing CW spike
            {
                ps.sm_tbl_ptr--;
            }
        }
    }
}

/**
 * @brief Is the logically DIT pressed (may reverse logic of HW contacts)
 */
static bool CwGen_DitRequested(void)
{
	bool retval;
	if(ts.cw_paddle_reverse)      // Paddles ARE reversed
	{
		retval =  Board_PttDahLinePressed();
	}
	else        // Paddles NOT reversed
	{
		retval =  Board_DitLinePressed();
	}

	return retval;
}

/**
 * @brief Is the logically DAH pressed (may reverse logic of HW contacts)
 */
static bool CwGen_DahRequested(void)
{
	bool retval;
	if(!ts.cw_paddle_reverse)      // Paddles NOT reversed
	{
		retval =  Board_PttDahLinePressed();
	}
	else        // Paddles ARE reversed
	{
		retval =  Board_DitLinePressed();
	}

	return retval;
}

uint32_t CwGen_ReverseCode(uint32_t code)
{
	int n[10], c = 0, i = 0;
	uint32_t result = 0;

	while (code > 0)
	{
		n[c++] = code % 4;
		code /= 4;
	}
	for (i=0; i<c; i++)
	{
		result = result * 4 + n[i];
	}

	return result;
}

/*
 * our lookup table has only UperCase letters,
 * so convert every char from the buffer to UPER
 */
static inline uint8_t CwGen_TranslateToUperCase( uint8_t c )
{
    return ( c >= 'a' && c <= 'z' ) ? (c - 32) : c;
}

static void CwGen_CheckPaddleState(void)
{
   if (CwGen_DahRequested())
   {
      ps.port_state |= CW_DAH_L;
      if(ts.cw_keyer_speed != ts.cw_keyer_speed_bak)
      {
          ts.cw_keyer_speed = ts.cw_keyer_speed_bak;
          CwGen_SetSpeed();
      }
   }

   if (CwGen_DitRequested())
   {
      ps.port_state |= CW_DIT_L;
      if(ts.cw_keyer_speed != ts.cw_keyer_speed_bak)
      {
          ts.cw_keyer_speed = ts.cw_keyer_speed_bak;
          CwGen_SetSpeed();
      }
   }
}

static void CwGen_CheckDigiBufferState(void)
{
	uint8_t c;

    /** @todo > Refactor, no way to figure out what and why */
    if ( !( ps.sending_char )
         && (! (ps.port_state & CW_END_PROC) && ( ps.space_timer < ( ps.space_time - ps.dah_time ))))
    {
        if ( DigiModes_TxBufferRemove( &c, CW ))
        {
            c = CwGen_TranslateToUperCase( c );
            for (int i = 0; i<CW_CHAR_CODES; i++)
            {
                if (cw_char_chars[i] == c)
                {
                    ps.sending_char = CwGen_ReverseCode(cw_char_codes[i]);
                    /**
                     * If dispatched character is a SPACE just set space timer.
                     * If not, request TxOn
                     */
                    if ( ps.sending_char == 1 )
                    {
                        ps.space_timer = ps.space_time;
                    }
                    else
                    {
                        RadioManagement_Request_TxOn();
                    }
                    break;
                }
            }
        }
    }

    // shift pseudo code further and simulate pressing one of paddles.
    if ( ps.sending_char > 1  )
    {
        uint32_t t = ps.sending_char % 4;
        if ( t == 3 )
        {
            ps.port_state |= CW_DAH_L;
            ps.sending_char /= 4;
        }
        else
        {
            ps.port_state |= CW_DIT_L;
            ps.sending_char /= 4;
        }
    }
}

/**
 * @brief called every 667u (== 1500Hz) from I2S IRQ, does cw tone generation
 * @returns true if a tone is currently being active, false if silence/no tone is requested
 */
bool CwGen_Process( float32_t *i_buffer, float32_t *q_buffer, uint32_t blockSize )
{
	bool retval;

	if(( ts.cw_keyer_mode == CW_KEYER_MODE_STRAIGHT ) || CatDriver_CatPttActive())
	{
		// we make sure the remaining code will see the "right" keyer mode
		// since we are running in an interrupt, none will change that outside
		// and we can safely restore after we're done
		uint8_t keyer_mode = ts.cw_keyer_mode;
		ts.cw_keyer_mode = CW_KEYER_MODE_STRAIGHT;
		retval = CwGen_ProcessStraightKey(i_buffer,q_buffer,blockSize);
		ts.cw_keyer_mode = keyer_mode;
	}
	else
	{
		retval = CwGen_ProcessIambic(i_buffer,q_buffer,blockSize);
	}
	return retval;
}

static bool CwGen_ProcessStraightKey( float32_t *i_buffer, float32_t *q_buffer, uint32_t blockSize )
{
	uint32_t retval;

	// simulate paddle interrupt if we do virtual keying via CAT / RS232
	if (( CatDriver_CatPttActive( ) && CatDriver_CWKeyPressed( )) || Board_PttDahLinePressed( ))
	{
       if( ps.key_timer == 0 )
       {
          ps.sm_tbl_ptr  = 0;			// smooth table start
          ps.key_timer   = 3;			// rising edge
          CwGen_SetBreakTime();
       }
	}

	// Exit to RX if key_timer is zero and break_timer is zero as well
	if(ps.key_timer == 0)
	{
		if(ps.break_timer > 0)
		{
			if (--ps.break_timer == 0)
			{
			    RadioManagement_Request_TxOff();
			}
		}
		retval = false;
	}
	else
	{
		softdds_runIQ(i_buffer,q_buffer,blockSize);
		// ----------------------------------------------------------------
		// Raising slope
		//
		// Smooth start of element
		// key_timer is used to track signal phases for Straight Key mode
		// 	3: rising edge, 2: constant signal, 1: falling edge, 0: signal off
		// key_timer set to 3 (rising edge) in Paddle DAH IRQ
		// on every audio driver sample request shape the form
		// then stop at key_timer = 2 (constant signal)
		if(ps.key_timer > 2)
		{
			CwGen_RemoveClickOnRisingEdge(i_buffer,q_buffer,blockSize);
			if( ps.sm_tbl_ptr >= CW_SMOOTH_TBL_SIZE ) // end of rising edge when pointer at end of table
			{
				ps.key_timer = 2;	// at end of rising edge change to constant signal phase
			}
		}
		// -----------------------------------------------------------------
		// Middle of a symbol - no shaping, just
		// pass soft DDS data (key_timer = 2)
		// ..................
		// -----------------------------------------------------------------
		// Failing edge
		//
		// Do smooth the falling edge
		// key was released, so key_timer goes from 2 to zero
		// then finally switch to RX is performed (here, but on next request)
		if(ps.key_timer < 2)
		{
			CwGen_RemoveClickOnFallingEdge(i_buffer,q_buffer,blockSize);
			if(ps.sm_tbl_ptr == 0) // end of falling edge when pointer at end of table
			{
				CwGen_SetBreakTime();
				ps.key_timer = 0;
			}
		}

		// Key released ?, then shape falling edge until the end of the smooth table is reached
		if
		(
				( ps.key_timer == 2 )
				&&
				(
						( Board_PttDahLinePressed() == false )
						&&
						( CatDriver_CatPttActive() == false || CatDriver_CWKeyPressed() == false )
				)
		)
		{
			ps.key_timer = 1;	// begin of falling edge
		}
		retval = true;
	}
	return retval;
}

//------------------------------------------------------------------
//
// The Character Identification Function applies dot/dash pattern
// recognition to identify the received character.
//
// The function returns the ASCII code for the character received,
// or 0xff if pattern was not recognized.
//
//------------------------------------------------------------------
uint8_t CwGen_CharacterIdFunc(uint32_t code)
{
	uint8_t out = 0xff; // 0xff selected to indicate ERROR
	// Should never happen - Empty, spike suppression or similar
	if (code == 0)
	{
		out = 0xfe;
	}

	for (int i = 0; i<CW_CHAR_CODES; i++)
	{
		if (cw_char_codes[i] == code)
		{
			out = cw_char_chars[i];
			break;
		}
	}

	if (out == 0xff)
	{
		for (int i = 0; i<CW_SIGN_CODES; i++)
		{
			if (cw_sign_codes[i] == code)
			{
				out = cw_sign_onechar[i];
				break;
			}
		}
	}

	return out;
}

/*
 * Pushed proper char into digi_buffer.
 */
static void CwGen_AddChar( uint32_t code )
{
	char result = '*';
	// need to use a character which is both in morse and rtty

	for ( uint32_t i = 0; i < CW_CHAR_CODES; i++ )
	{
		if ( cw_char_codes[i] == code )
		{
			result = cw_char_chars[i];
            DigiModes_TxBufferPutChar( result, CW );
			break;
		}
	}

	if ( result == '*' )
	{
		for ( uint32_t i = 0; i < CW_SIGN_CODES; i++ )
		{
			if (cw_sign_codes[i] == code)
			{
				result = '-';
                DigiModes_TxBufferPutSign( cw_sign_chars[i], CW );
				break;
			}
		}
		if ( result == '*' )
		{
		    DigiModes_TxBufferPutChar( '*', CW );
		}
	}

	ps.cw_char = 0;
}

static bool CwGen_ProcessIambic( float32_t* i_buffer, float32_t* q_buffer, uint32_t blockSize )
{
	uint32_t retval = false;

	bool rerunStateMachine = false;
	// we use this variable to indicate immediate rerun of the state machine
	// only if we actively generate silence or signal, we do not rerun the machine immediately.

	do {
		rerunStateMachine = false;

		switch(ps.cw_state)
		{
		case CW_IDLE:
		{
			rerunStateMachine = false;

            CwGen_CheckPaddleState();
            CwGen_CheckDigiBufferState();
			// at least one paddle is still or has been recently pressed
			if( ps.port_state & ( CW_DAH_L | CW_DIT_L ))
			{
			    // Note if Dit/Dah is discriminated in this function, it breaks the Iambic-ness!
				ps.cw_state = CW_WAIT;
				rerunStateMachine = true;
			}
			else
			{
				if (ps.port_state & CW_END_PROC)
				{
                    if(ps.cw_char > 50000)
                    {
                        CwGen_AddChar(-1);
                    }
                    else
                    {
                        CwGen_AddChar(ps.cw_char);
                    }
					ps.port_state &= ~CW_END_PROC;
					ps.space_timer = ps.space_time;
				}

				if (ps.space_timer > 0)
				{
					if (--ps.space_timer == 0)
					{
						CwGen_AddChar(1);
						if (ps.sending_char == 1)
						{
							ps.sending_char = 0;
						}
					}
				}

				if ( ps.break_timer > 0 /*&& !ps.sending_char*/)
				{
					if (--ps.break_timer == 0)
					{
					    RadioManagement_Request_TxOff();
					}
				}

				retval = false;
			}
		}
		break;
		// This is an extra state called after detection of an element to
		// allow the other state machines to settle.
		case CW_WAIT:
		{
			// It is NECESSARY to eliminate a "glitch" at the beginning of the
		    // first Iambic Morse DIT element in a string!
		    if ( ts.txrx_mode == TRX_MODE_TX || ts.cw_text_entry )
		    {
                ps.cw_state = CW_DIT_CHECK;
                rerunStateMachine = true;
		    }
		}
		break;
		case CW_DIT_CHECK:
		{
			if (ps.port_state & CW_DIT_L)
			{
				ps.port_state |= CW_DIT_PROC;
				ps.key_timer   = ps.dit_time;
				ps.cw_state    = CW_KEY_DOWN;
				ps.cw_char     = ps.cw_char * 4 + 2;
			}
			else
			{
				ps.cw_state = CW_DAH_CHECK;
			}
			rerunStateMachine = true;
		}
		break;
		case CW_DAH_CHECK:
		{
			if (ps.port_state & CW_DAH_L)
			{
				ps.key_timer = ps.dah_time;
				ps.cw_char   = ps.cw_char * 4 + 3;
				ps.cw_state  = CW_KEY_DOWN;
			}
			else
			{
				CwGen_SetBreakTime();
                ps.port_state |= CW_END_PROC;
				ps.cw_state    = CW_IDLE;
			}
			rerunStateMachine = true;
		}
		break;
		case CW_KEY_DOWN:
		{
			softdds_runIQ( i_buffer, q_buffer, blockSize );
			ps.key_timer--;

			// Smooth start of element - initial
			ps.sm_tbl_ptr = 0;
			CwGen_RemoveClickOnRisingEdge( i_buffer, q_buffer, blockSize );

			ps.port_state &= ~( CW_DIT_L + CW_DAH_L );
			ps.cw_state    = CW_KEY_UP;
			retval = true;
		}
		break;
		case CW_KEY_UP:
		{
			if(ps.key_timer == 0)
			{
				ps.key_timer = ps.pause_time;
				ps.cw_state  = CW_PAUSE;
			}
			else
			{
				softdds_runIQ(i_buffer,q_buffer,blockSize);
				ps.key_timer--;

				// Smooth start of element - continue
				if(ps.key_timer > (ps.dit_time/2))
				{
					CwGen_RemoveClickOnRisingEdge(i_buffer,q_buffer,blockSize);
				}
				// Smooth end of element
//				if(ps.key_timer < CW_SMOOTH_STEPS)
				if(ps.key_timer < ts.cw_smooth_steps)
				{
					CwGen_RemoveClickOnFallingEdge(i_buffer,q_buffer,blockSize);
				}

				if(ts.cw_keyer_mode == CW_KEYER_MODE_IAM_B)
				{
                    CwGen_CheckPaddleState();
				}
				retval = true;
			}
		}
		break;
		case CW_PAUSE:
		{
			ps.key_timer--;
			if(ps.key_timer == 0)
			{
                CwGen_CheckPaddleState();

				if (ts.cw_keyer_mode == CW_KEYER_MODE_IAM_A || ts.cw_keyer_mode == CW_KEYER_MODE_IAM_B)
				{
					if (ps.port_state & CW_DIT_PROC)
					{
						ps.port_state &= ~(CW_DIT_L + CW_DIT_PROC);
						ps.cw_state    = CW_DAH_CHECK;
					}
					else
					{
                        ps.port_state |= CW_END_PROC;

						ps.port_state &= ~(CW_DAH_L);
						CwGen_SetBreakTime();
						ps.cw_state    = CW_IDLE;
					}
				}
				else // CW_KEYER_MODE_ULTIMATE
				{
					CwGen_TestFirstPaddle();
                    if((ps.port_state & CW_DAH_L) && ps.ultim == 0)
                    {
                        ps.port_state &= ~(CW_DIT_L + CW_DIT_PROC);
                        ps.cw_state   = CW_DAH_CHECK;
                    }
                    else
                    {
                        ps.port_state |= CW_END_PROC;
                        ps.port_state &= ~(CW_DAH_L);
                        CwGen_SetBreakTime();
                        ps.cw_state    = CW_IDLE;
                    }
				}
				rerunStateMachine = true;
			}
		}
		break;
		default:
			break;
		}
	} while (rerunStateMachine);

	return retval;
}


static void CwGen_TestFirstPaddle(void)
{
	if(ts.cw_keyer_mode == CW_KEYER_MODE_ULTIMATE)
	{
		if(!CwGen_DitRequested() && CwGen_DahRequested())
		{
			ps.ultim = 1;
		}

		if(CwGen_DitRequested() && !CwGen_DahRequested())
		{
			ps.ultim = 0;
		}
	}
}

void CwGen_ResetBufferSending()
{
	if (!ts.cw_text_entry && ts.dmod_mode == DEMOD_CW)
	{
		DigiModes_TxBufferReset();
		ts.keyer_cycled_tx_active = false;
		ps.sending_char = 0;
	}
}

/**
 * @brief request switch to TX and sets timers, only does something if not in straight key mode
 */
void CwGen_DitIRQ(void)
{
    CwGen_ResetBufferSending();

    // CW mode handler - no dit interrupt in straight key mode
    if(ts.cw_keyer_mode != CW_KEYER_MODE_STRAIGHT)
    {
        CwGen_TestFirstPaddle();
    }
}

/**
 * @brief request switch to TX and sets timers
 */
void CwGen_DahIRQ(void)
{
    CwGen_DitIRQ();
}
