/*-------------------------------------------------------------------------*\

	FILE........: DTMF.C
	TYPE........: C Module
	COMPANY.....: VoiceTronix
	AUTHOR......: John kostogiannis
	DATE CREATED: 8/8/96

	High level logic for fixed point DTMF decoder.

\*-------------------------------------------------------------------------*/

#include	"dtmf.h"
#include	"decfr.h"
#include	"dtmfcomm.h"
#include	"dtmfs.h"
#include	"gen.h"

#include	<stdlib.h>
#include	<stdio.h>
#include	<math.h>
#include	<assert.h>
#include	<string.h>

/* State machine states ------------------------------------------------*/

#define	SILA		0	/* silence before tone			*/
#define	TONEA		1	/* tone present (full frame of tone)	*/
#define	TONEB		2	/* tone present (full frame of tone)	*/
#define	TONEC		3	/* tone present (full frame of tone)	*/
#define	TONED		4	/* tone present (full frame of tone)	*/
#define	SILB		5	/* silence after tone			*/
#define SILC		6	/* silence after frame			*/

/* low level logic defines ---------------------------------------------*/

#define NFREQ		8	/* number of DTMF filters		*/

#define	ROW		4	/* last row filter + 1			*/
#define	COL		8	/* last column filter + 1		*/
#define HARMOFF		8	/* offset to 2nd harmonic transforms	*/

/* experimentally derived constants in float form ----------------------*/

//#define LEVLO		1.22E9	/* cutoff level of -35dBm? To sensitive	*/
//#define LEVLO		5.22E9	/* cutoff level - works with ADSL	*/
#define LEVLO		8.71E9	/* cutoff level - works with ADSL and VoiceCom	*/
//#define LEVLO		1.22E10	/* cutoff level - Doesnt work with ADSL	*/
//#define LEVLO		1.22E11	/* cutoff level of -35dBm		*/
#define FWDTWST		63.1E-3	/* low tone > than high tone by 12dB	*/
#define REVTWST		0.1585	/* high tone > low tone by 8dB		*/
#define FRMHLF		0.5623	/* half frame energies within 2.5dB	*/
#define ETONES		0.631	/* tone energy cf energy in frame -2dB	*/
#define ENV		0.1	/* tone on/off envelope constant	*/

/* experimentally derived constants in fixed form ----------------------*/

#define FLT2WORD16L( X, Q)	(int16_t)(X * (1l<<Q))
#define FLT2WORD16R( X, Q)	(int16_t)(X / (1l<<Q))

#define	LEVLO_Q         26
#define	LEVLO_FIX	FLT2WORD16R(LEVLO, LEVLO_Q)

#define	FWDTWST_Q	15
#define	FWDTWST_FIX	FLT2WORD16L(FWDTWST, FWDTWST_Q)

#define	REVTWST_Q	15
#define	REVTWST_FIX	FLT2WORD16L(REVTWST, REVTWST_Q)

#define	FRMHLF_Q	15
#define	FRMHLF_FIX	FLT2WORD16L(FRMHLF, FRMHLF_Q)

#define	ETONES_Q	0
#define	ETONES_FIX	FLT2WORD16R((ETONES*NDTMF/2), ETONES_Q)

#define	ENV_Q		15
#define	ENV_FIX		FLT2WORD16L(ENV, ENV_Q)


/* this array maps a key code to the ASCII code */

static char asciikey[] = {'1','2','3','A',
			  '4','5','6','B',
			  '7','8','9','C',
			  '*','0','#','D'};

/* Error codes in string form for debugging */

#ifdef __TURBOC__
static char *retcode_strings[] = {
"DTMF_VALID  ",
"DTMF_BUF    ",
"DTMF_LEVEL  ",
"DTMF_FWDTWST",
"DTMF_REVTWST",
"DTMF_ROWOFF ",
"DTMF_COLOFF ",
"DTMF_FRMHALF1",
"DTMF_FRMHALF2",
"DTMF_ETONES"
};

static char *state_strings[] = {
"SILA",
"TONEA",
"TONEB",
"TONEC",
"TONED",
"SILB",
"SILC",
};

static FILE *flog;	/* error logging file */
static int  frames;	/* frame counter for debug only	*/
#endif


int process_frame(DTMFS *st, int *key);


/*---------------------------------------------------------------------------*\

	FUNCTION....: dtmf_init()

	AUTHOR......: David Rowe
	DATE CREATED: 9/9/96

	Allocates storage for DTMF decoder state variables, and initailises.
	A void pointer to the state variables is returned.  When finished with
	the DTMF decoder, this memory can be recovered using the C free()
	function.

\*---------------------------------------------------------------------------*/

void *dtmf_init() {
    DTMFS  *st;
    int    i;

    /* only even frame sizes allowed in decode_frame_asm() */

    assert((NDTMF%2) == 0);

    /* allocate storage */

    st = (DTMFS*)malloc(sizeof(DTMFS));
    assert(st != NULL);

    /* initialse state variables */

    st->samples  = 0;
    st->testmode = 0;
    st->state = SILA;
    st->code = -1;
    for(i=0; i<DMEM; i++) {
	st->states[i] = 0;
	st->retcodes[i] = 0;
    }
    st->e = 0.0;
    st->etone = 0.0;

    return(st);
}

/*---------------------------------------------------------------------------*\

	FUNCTION....: dtmf_close()

	AUTHOR......: David Rowe
	DATE CREATED: 25/11/97

	Frees memory allocated to DTMF decoder.

\*---------------------------------------------------------------------------*/

void dtmf_close(void *pv) {
    DTMFS  *st = (DTMFS*)pv;

    assert(st != NULL);
    free(st);
}

/*---------------------------------------------------------------------------*\

	FUNCTION....: size_of_DTMF()

	AUTHOR......: David Rowe
	DATE CREATED: 14/10/96

	Returns the size of the DTMF state variables structure.  Useful for
	when routines external to the DTMF code need to copy the state
	variales.

\*---------------------------------------------------------------------------*/

int dtmf_size_of() {
    return(sizeof(DTMFS));
}

/*---------------------------------------------------------------------------*\

	FUNCTION....: testmode()

	AUTHOR......: David Rowe
	DATE CREATED: 14/10/96

	Turns the DTMF test mode on (mode==1) or off (mode==0).

\*---------------------------------------------------------------------------*/

void dtmf_testmode(void *s, int mode) {
    DTMFS  *st = (DTMFS*)s;

    st->testmode = mode;
    #ifdef __TURBOC__
    flog = fopen("log.txt","wt");
    frames = 0;
    #endif
}

/*---------------------------------------------------------------------------*\

	FUNCTION....: dtmf_decoder()

	AUTHOR......: John Kostogiannis and David Rowe
	DATE CREATED: 6/9/96

	DTMF decoder function.  Returns the number of DTMF tones decoded.  This
	function contains the high level state machine logic and calls the
	filtering and low level function decode_frame().

\*---------------------------------------------------------------------------*/

int dtmf_decoder( void *s,
		  char keys[],
		  unsigned short maxkeys,
		  short ibuf[],
		  unsigned short n,
		  int *keydown )
/*  void   *s;		dtmf decoder state variables		*/
/*  char   keys[];	ASCII DTMF keys in buf[]		*/
/*  int    maxkeys[];	size of keys[] buffer			*/
/*  short  ibuf[];     	buffer of input samples 		*/
/*  int    n;		size of input buffer			*/
{
    DTMFS  *st;		/* DTMF state variables			*/
    int    i,j;
    int    tocopy;	/* num samples to copy into x		*/
    int    retcode;	/* return code from decoder 		*/
    int    ok;		/* asserted if valid code returned	*/
    int    steady;	/* tone is steady over 2 frames		*/
    int    onset;	/* tone has just started		*/
    int    env;		/* asserted when envelope OK		*/
    int    key = -1;	/* potential key if valid tone		*/

    assert(s != NULL);

    st = s;
    i = 0;
    *keydown=0;

    while(n) {
	tocopy = smin(NDTMF-st->samples,n);
	memcpy(&st->x[st->samples], ibuf, tocopy*sizeof(short));
	(st->samples) += tocopy;
	ibuf += tocopy;
	n -= tocopy;

	if ((st->samples == NDTMF) && (i < maxkeys-1)) {

	    retcode = process_frame(st, &key);

	    /* store debug state variable info */

	    for(j=0; j<DMEM-1; j++) {
		st->states[j] = st->states[j+1];
		st->retcodes[j] = st->retcodes[j+1];
	    }
	    st->states[j] = st->state;
	    st->retcodes[j] = retcode;

	    /* set up state machine input variables */

	    ok = onset = steady = env = 0;
	    if (retcode == DTMF_VALID) {
		ok = 1;
		if (key == st->code)
		    steady = 1;
		else
		    onset = 1;
	    }
	    else
		if (st->env)
		    env = 1;

	    /* printf's used for development */

	    if (st->testmode) {
		#ifdef __TURBOC__
		fprintf(flog,"\n");
		if (retcode < 0)
		    fprintf(flog,"retcode: %s  state: %s ok: %d env: %d\n",
                                 retcode_strings[-retcode],
			         state_strings[st->state], ok, env);
		else
		    fprintf(flog,"key: %d  state: %s ok: %d env: %d \n",
                                 key,state_strings[st->state], ok, env);
		#endif
	    }

	    /* state machine to determine if returned code is valid */
	    switch(st->state) {
		case SILA:
		    if (onset) {
			st->state = TONEA;
			st->code = key;
			st->etone = st->e;
		    } else {
			st->code = -1;
			st->state = SILA;
		    }
		    break;

		case TONEA:
		    if(steady) st->state = TONEB;
		    else       st->state = SILA;
		    break;

		case TONEB:
		    if(steady) {
			st->state = TONEC;
                        *keydown = asciikey[st->code];
                    }
		    if(!ok)   st->state = SILB;
		    if(onset) st->state = SILA;
		    break;

		case TONEC:
		    if(steady) st->state = TONED;
		    if(!ok)    st->state = SILB;
		    if(onset)  st->state = SILA;
		    break;

		case TONED:
		    if(steady) st->state = TONED;
		    if(!ok)    st->state = SILB;
		    if(onset)  st->state = SILA;
		    break;

		case SILB:
		    /*
		    if (env)
			st->state = SILC;
		    else
			st->state = SILA;
		    */
		    if(!ok) st->state = SILC;
		    else    st->state = SILA;

		    break;
		case SILC:
		    st->state = SILA;
		    if (env) {
			keys[i++] = asciikey[st->code];
			#ifdef __TURBOC__
			if (st->testmode)
			    fprintf(flog,"KEY DETECTED\n");
			#endif
		    }
		    break;
	    }

	    /* reset no. samples in buffer */
	    st->samples = 0;
	    if (st->testmode) {
		#ifdef __TURBOC__
		fprintf(flog,"n = %d---------------------------------------\n\n",n);
		#endif
	    }
	}
    }

    keys[i] = 0;		/* null terminator */
    return(i);
}

/*---------------------------------------------------------------------------*\

	FUNCTION....: process_frame()

	AUTHOR......: David Rowe
	DATE CREATED: 31/10/97

	Calls the filtering routine, and performs low level logic tests.
	Returns a key code between 0 and 15, or a negative failure code.

\*---------------------------------------------------------------------------*/

int process_frame(DTMFS *st, int *key)
/*  DTMFS  *st;		DTMF state variables			*/
/*  int    *key;	key detected if frame valid		*/
{
    int    i;
//    int    j;
//    float  y;		/* current output sample, y(n)		*/
//    int16_t ymem[2];	/* ymem[0] = y(n-1), ymem[1] = y(n-2)	*/
//    int    k;		/* 0->15 code representing current key	*/
    int16_t dtcf[NFREQ];/* DTCF mag squared for each frequency	*/
    int16_t rowmax;	/* maximum value of row transforms	*/
    int     rowmx;	/* index [0..3] of rowmax		*/
    int16_t colmax;	/* maximum value of column transforms	*/
    int     colmx;	/* index [0..3] of colmax		*/
    int16_t total;	/* total in DTMF tone pair transforms	*/
    int16_t E1,E2;	/* energy of frame halves		*/
    int32_t acc,p;	/* simulated C5x acc and p reg		*/

    /* call filtering and energy calc function */

    decode_frame(dtcf, &E1, &E2, st->x);

    /* perform validation logic --------------------------------------------*/

    /* Determine row (low freq) peak */

    rowmax = dtcf[0];
    rowmx = 0;
    for(i=1; i<ROW; i++)
	if (dtcf[i] > rowmax) {
	    rowmax = dtcf[i];
	    rowmx = i;
	}

    /* Determine column (high freq) peak */

    colmax = dtcf[ROW];
    colmx = ROW;
    for(i=ROW+1; i<COL; i++)
	if (dtcf[i] > colmax) {
	    colmax = dtcf[i];
	    colmx = i;
	}
    acc = (long)colmax+(long)rowmax;		/* 16.0 */
    if (acc > 0x7fffl) acc = 0x7fffl;
    total = acc;

    /* total *= pow(2.0,28); */

    /* sample frame energy for use in state machine */

    acc = E1+E2;
    if (acc > 0x7fffl) acc = 0x7fffl;
    st->e = total;

    /* end of tone energy envelope detector */

    /*
    if ((float)st->e < 0.1*(float)st->etone)
	st->env = 1;
    else
	st->env = 0;
    */

    p = (long)st->etone * (long)ENV_FIX;
    p *= 2;
    acc = ((p*8)>>16) - st->e*8;
    if (acc > 0)
	st->env = 1;
    else
	st->env = 0;

    /* print out debug info if enabled */

    if (st->testmode) {
	#ifdef __TURBOC__
	fprintf(flog,"FRAME: %d\n", frames++);
	fprintf(flog,"ROW bins         : ");
	for(i=0; i<ROW; i++)
	    fprintf(flog,"%e  ",(float)dtcf[i]);
	fprintf(flog,"\n");
	fprintf(flog,"COL bins         : ");
	for(i=ROW; i<COL; i++)
	    fprintf(flog,"%e  ",(float)dtcf[i]);
	fprintf(flog,"\n");

	fprintf(flog,"rowmax: %e rowmx: %2d\n",(float)rowmax,rowmx);
	fprintf(flog,"colmax: %e colmx: %2d\n",(float)colmax,colmx);

	fprintf(flog,"\n");
	fprintf(flog,"E1: %e  E2: %e st->etone: %e\n",(float)E1,(float)E2, (float)st->etone);
	fprintf(flog,"total: %e  (E1+E2)*NDTMF/2: %e\n",(float)total,(float)(E1+E2)*NDTMF/2);
	#endif
    }

    /* Valid signal strength, fail if total less than LEVLO */

    /*
    if (total*pow(2.0,28) < LEVLO)
	return(DTMF_LEVLO);
    */
    if (total < LEVLO_FIX)
	return(DTMF_LEVLO);

    /* foward twist, low tone (row) can be greater than high by FWDTWST */

    /*
    if (rowmax*FWDTWST > colmax)
	return(DTMF_FWDTWST);
    */

    p = (long)rowmax * (long)FWDTWST_FIX;
    p *= 2;
    acc = (p>>16) - colmax;
    if (acc > 0)
	return(DTMF_FWDTWST);

    /* reverse twist, high tone (column) can be greater than low by REVTWST */

    /*
    if (colmax*REVTWST > rowmax)
	return(DTMF_REVTWST);
    */

    p = (long)colmax * (long)REVTWST_FIX;
    p *= 2;
    acc = (p>>16) - rowmax;

    if (acc > 0)
	return(DTMF_REVTWST);

    /* Energy of frame halves */

    /*
    if ((float)E2*FRMHLF > (float)E1)
	return(DTMF_FRMHALF1);
    */

    p = (long)E2 * (long)FRMHLF_FIX;
    p *= 2;
    acc = ((p+0x8000l)>>16) - E1;

    if (acc > 0)
	return(DTMF_FRMHALF1);

    /*
    if ((float)E1*FRMHLF > (float)E2)
	return(DTMF_FRMHALF2);
    */
    p = (long)E1 * (long)FRMHLF_FIX;
    p *= 2;
    acc = (p>>16) - E2;
    if (acc > 0)
	return(DTMF_FRMHALF2);

    /* Energy in tones compared to energy in frame */

#define	E1E2	(0x7fff/(NDTMF/2))

    /*
    if (ETONES*((float)E1+(float)E2)*NDTMF/2 > total)
	return(DTMF_ETONES);
    */
    acc = E1 + E2;
    if (acc > E1E2) acc = E1E2;
    p = (long)acc * (long)ETONES_FIX;
    if (p > 0x7fffl) p = 0x7fffl;
    acc = p-total;
    if (acc > 0)
	return(DTMF_ETONES);

    /* All tests OK, so return key code */

    *key = rowmx*4 + (colmx-4);
    return(DTMF_VALID);

}

