/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
 * ----------------------------------------------------------------------------
 *
 * Extract DTMF signals from 16 bit PCM audio
 *
 * Originally written by Poul-Henning Kamp <phk@freebsd.org>
 * Made into a C++ class by Roger Hardiman <roger@freebsd.org>, January 2002
 *
 * $Log: dtmf.cxx,v $
 * Revision 1.12.2.1  2005/02/04 05:19:09  csoutheren
 * Backported patches from Atlas-devel
 *
 * Revision 1.13  2005/01/25 06:35:27  csoutheren
 * Removed warnings under MSVC
 *
 * Revision 1.12  2004/11/17 10:13:14  csoutheren
 * Fixed compilation with gcc 4.0.0
 *
 * Revision 1.11  2004/09/09 23:50:49  csoutheren
 * Fixed problem with duplicate definition of sinetab causing problems
 *
 * Revision 1.10  2004/09/09 05:23:38  dereksmithies
 * Add utility function to report on dtmf characters used.
 *
 * Revision 1.9  2004/09/09 04:22:46  csoutheren
 * Added sine table for DTMF encoder
 *
 * Revision 1.8  2004/09/09 04:00:01  csoutheren
 * Added DTMF encoding functions
 *
 * Revision 1.7  2003/03/17 07:39:25  robertj
 * Fixed possible invalid value causing DTMF detector to crash.
 *
 * Revision 1.6  2002/02/20 02:59:34  yurik
 * Added end of line to trace statement
 *
 * Revision 1.5  2002/02/12 10:21:56  rogerh
 * Stop sending '?' when a bad DTMF tone is detected.
 *
 * Revision 1.4  2002/01/24 11:14:45  rogerh
 * Back out robert's change. It did not work (no sign extending)
 * and replace it with a better solution which should be happy on both big
 * endian and little endian systems.
 *
 * Revision 1.3  2002/01/24 10:40:17  rogerh
 * Add version log
 *
 *
 */

#ifdef __GNUC__
#pragma implementation "dtmf.h"
#endif

#include <ptlib.h>
#include <ptclib/dtmf.h>

#include <math.h>

/* Integer math scaling factor */
#define FSC	(1<<12)

/* This is the Q of the filter (pole radius) */
#define POLRAD .99

#define P2 ((int)(POLRAD*POLRAD*FSC))



PDTMFDecoder::PDTMFDecoder()
{
	// Initialise the class
	int i,kk;
	for (kk = 0; kk < 8; kk++) {
		y[kk] = h[kk] = k[kk] = 0;
	}

	nn = 0;
	ia = 0;
	so = 0;

	for (i = 0; i < 256; i++) {
		key[i] = '?';
	}

	/* We encode the tones in 8 bits, translate those to symbol */
	key[0x11] = '1'; key[0x12] = '4'; key[0x14] = '7'; key[0x18] = '*';
	key[0x21] = '2'; key[0x22] = '5'; key[0x24] = '8'; key[0x28] = '0';
	key[0x41] = '3'; key[0x42] = '6'; key[0x44] = '9'; key[0x48] = '#';
	key[0x81] = 'A'; key[0x82] = 'B'; key[0x84] = 'C'; key[0x88] = 'D';

	/* The frequencies we're trying to detect */
	/* These are precalculated to save processing power */
	/* static int dtmf[8] = {697, 770, 852, 941, 1209, 1336, 1477, 1633}; */
	/* p1[kk] = (-cos(2 * 3.141592 * dtmf[kk] / 8000.0) * FSC) */
	p1[0] = -3497; p1[1] = -3369; p1[2] = -3212; p1[3] = -3027;
	p1[4] = -2384; p1[5] = -2040; p1[6] = -1635; p1[7] = -1164;
}


PString PDTMFDecoder::Decode(const void *buf, PINDEX bytes)
{
	int x;
	int s, kk;
	int c, d, f, n;
	short *buffer = (short *)buf;

	PINDEX numSamples = bytes >> 1;

	PString keyString;

	PINDEX pos;
	for (pos = 0; pos < numSamples; pos++) {

		/* Read (and scale) the next 16 bit sample */
		x = ((int)(*buffer++)) / (32768/FSC);

		/* Input amplitude */
		if (x > 0)
			ia += (x - ia) / 128;
		else
			ia += (-x - ia) / 128;

		/* For each tone */
		s = 0;
		for(kk = 0; kk < 8; kk++) {

			/* Turn the crank */
			c = (P2 * (x - k[kk])) / FSC;
			d = x + c;
			f = (p1[kk] * (d - h[kk])) / FSC;
			n = x - k[kk] - c;
			k[kk] = h[kk] + f;
			h[kk] = f + d;

			/* Detect and Average */
			if (n > 0)
				y[kk] += (n - y[kk]) / 64;
			else
				y[kk] += (-n - y[kk]) / 64;

			/* Threshold */
			if (y[kk] > FSC/10 && y[kk] > ia)
				s |= 1 << kk;
		}

		/* Hysteresis and noise supressor */
		if (s != so) {
			nn = 0;
			so = s;
		} else if (nn++ == 520 && s < 256 && key[s] != '?') {
			PTRACE(3,"DTMF\tDetected '" << key[s] << "' in PCM-16 stream");
			keyString += key[s];
		}
	}
	return keyString;
}

////////////////////////////////////////////////////////////////////////////////////////////

//
//  implement a PCM tone generator
//
//  For reference, the US tones are (as indictated by http://www.elexp.com/t_tele.htm)
//
//   Dial Tone 350 Hz + 440 Hz Continuous 
//   Ring Back 440 Hz + 480 Hz ON 2.0, OFF 4.0 seconds 
//   Busy 480 Hz + 620 Hz On 0.5, OFF 0.5 seconds 
//

// this code is based on code copied from http://www-users.cs.york.ac.uk/~fisher/telecom/tones/teletones.C


#define   DTMF_LEN  100

#ifndef M_PI
#define M_PI        3.1415926
#endif

#define TWOPI	      (2.0 * M_PI)
#define MAXSTR	     512
#define SAMPLERATE   8000
#define SINEBITS     11
#define SINELEN	     (1 << SINEBITS)
#define TWO32	      4294967296.0	/* 2^32 */


static double amptab[2] = { 8191.75, 16383.5 };

static inline int ifix(double x) 
{ 
  return (x >= 0.0) ? (int) (x+0.5) : (int) (x-0.5); 
}

// given frequency f, return corresponding phase increment 
static inline int phinc(double f)
{ 
  return ifix(TWO32 * f / (double) SAMPLERATE);
}

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

char PDTMFEncoder::DtmfChar(PINDEX i)
{
  PAssert(i < 16, "Only 16 dtmf symbols. Index too large");

  return dtmfSymbols[i];
}




// DTMF frequencies as per http://www.commlinx.com.au/DTMF_frequencies.htm

static double dtmfFreqs[16][2] = {
  { 941.0, 1336.0 },  // 0
  { 697.0, 1209.0 },  // 1
  { 697.0, 1336.0 },  // 2
  { 697.0, 1477.0 },  // 3
  { 770.0, 1209.0 },  // 4
  { 770.0, 1336.0 },  // 5
  { 770.0, 1477.0 },  // 6
  { 852.0, 1209.0 },  // 7
  { 852.0, 1336.0 },  // 8
  { 852.0, 1477.0 },  // 9
  { 697.0, 1633.0 },  // A
  { 770.0, 1633.0 },  // B
  { 852.0, 1633.0 },  // C
  { 941.0, 1633.0 },  // D
  { 941.0, 1209.0 },  // *
  { 941.0, 1477.0 }   // #
};

////////////////////////////////////////////////////////////////////////

double PDTMFEncoder::sinetab[1 << 11];

PMutex & PDTMFEncoder::GetMutex()
{
  static PMutex mutex;
  return mutex;
}

void PDTMFEncoder::MakeSineTable()
{ 
  PWaitAndSignal m(GetMutex());
  static BOOL sineTabInit = FALSE;

  if (!sineTabInit) {
    for (int k = 0; k < SINELEN; k++) { 
      double th = TWOPI * (double) k / (double) SINELEN;
      double v = sin(th);
      sinetab[k] = v;
    }
    sineTabInit = TRUE;
  }
}

void PDTMFEncoder::AddTone(char _digit, unsigned len)
{
  char digit = (char)toupper(_digit);
  if ('0' <= digit && digit <= '9')
    digit = (char)(digit - '0');

  else if ('A' <= digit && digit <= 'D')
    digit = (char)(digit + 10 - 'A');

  else if (digit == '*')
    digit = 14;

  else if (digit == '#')
    digit = 15;

  else
    return;

  AddTone(dtmfFreqs[(int)digit][0], dtmfFreqs[(int)digit][1], len);
}

void PDTMFEncoder::AddTone(const PString & str, unsigned len)
{
  PINDEX i;
  for (i = 0; i < str.GetLength(); i++)
    AddTone(str[i], len);
}

void PDTMFEncoder::AddTone(double f1, double f2, unsigned ms)
{
  int ak = 0;

  MakeSineTable();

  PINDEX dataPtr = GetSize();

  double amp = amptab[ak];
  int phinc1 = phinc(f1), phinc2 = phinc(f2);
  int ns = ms * (SAMPLERATE/1000);
  unsigned int ptr1 = 0, ptr2 = 0;

  for (int n = 0; n < ns; n++) { 

    double val = amp * (sine(ptr1) + sine(ptr2));
    int ival = ifix(val);
    if (ival < -32768)
      ival = -32768;
    else if (val > 32767) 
      ival = 32767;

    if (dataPtr == GetSize()) 
      SetSize(GetSize() + 1024);

    (*this)[dataPtr++] = (BYTE)(ival & 0xff);
    (*this)[dataPtr++] = (BYTE)(ival >> 8);

    ptr1 += phinc1; 
    ptr2 += phinc2;
  }

  SetSize(dataPtr);
}

////////////////////////////////////////////////////////////////////////////
