/* 
 * libteletone
 * Copyright (C) 2005/2006, Anthony Minessale II <anthmct@yahoo.com>
 *
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is tone_detect.c - General telephony tone detection, and specific detection of DTMF.
 *
 *
 * The Initial Developer of the Original Code is
 * Stephen Underwood <steveu@coppice.org>
 * Portions created by the Initial Developer are Copyright (C)
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * 
 * The the original interface designed by Steve Underwood was preserved to retain 
 *the optimizations when considering DTMF tones though the names were changed in the interest 
 * of namespace.
 *
 * Much less efficient expansion interface was added to allow for the detection of 
 * a single arbitrary tone combination which may also exceed 2 simultaneous tones.
 * (controlled by compile time constant TELETONE_MAX_TONES)
 *
 * Copyright (C) 2006 Anthony Minessale II <anthmct@yahoo.com>
 *
 *
 * libteletone_detect.c Tone Detection Code
 *
 *
 ********************************************************************************* 
 *
 * Derived from tone_detect.c - General telephony tone detection, and specific
 * detection of DTMF.
 *
 * Copyright (C) 2001  Steve Underwood <steveu@coppice.org>
 *
 * Despite my general liking of the GPL, I place this code in the
 * public domain for the benefit of all mankind - even the slimy
 * ones who might try to proprietize my work and use it to my
 * detriment.
 *
 *
 * Exception:
 * The author hereby grants the use of this source code under the 
 * following license if and only if the source code is distributed
 * as part of the openzap library.	Any use or distribution of this
 * source code outside the scope of the openzap library will nullify the
 * following license and reinact the MPL 1.1 as stated above.
 *
 * Copyright (c) 2007, Anthony Minessale II
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * * Neither the name of the original author; nor the names of any contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 * 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED.	 IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <libteletone_detect.h>

#ifndef _MSC_VER
#include <stdint.h>
#endif
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <fcntl.h>


static teletone_detection_descriptor_t dtmf_detect_row[GRID_FACTOR];
static teletone_detection_descriptor_t dtmf_detect_col[GRID_FACTOR];
static teletone_detection_descriptor_t dtmf_detect_row_2nd[GRID_FACTOR];
static teletone_detection_descriptor_t dtmf_detect_col_2nd[GRID_FACTOR];

static float dtmf_row[] = {697.0f,	770.0f,	 852.0f,  941.0f};
static float dtmf_col[] = {1209.0f, 1336.0f, 1477.0f, 1633.0f};

static char dtmf_positions[] = "123A" "456B" "789C" "*0#D";

static void goertzel_init(teletone_goertzel_state_t *goertzel_state, teletone_detection_descriptor_t *tdesc) {
	goertzel_state->v2 = goertzel_state->v3 = 0.0;
	goertzel_state->fac = tdesc->fac;
}

TELETONE_API(void) teletone_goertzel_update(teletone_goertzel_state_t *goertzel_state,
							  int16_t sample_buffer[],
							  int samples)
{
	int i;
	float v1;
	
	for (i = 0;	 i < samples;  i++) {
		v1 = goertzel_state->v2;
		goertzel_state->v2 = goertzel_state->v3;
		goertzel_state->v3 = (float)(goertzel_state->fac*goertzel_state->v2 - v1 + sample_buffer[i]);
	}
}
#ifdef _MSC_VER
#pragma warning(disable:4244)
#endif

#define teletone_goertzel_result(gs) (double)(((gs)->v3 * (gs)->v3 + (gs)->v2 * (gs)->v2 - (gs)->v2 * (gs)->v3 * (gs)->fac))

TELETONE_API(void) teletone_dtmf_detect_init (teletone_dtmf_detect_state_t *dtmf_detect_state, int sample_rate)
{
	int i;
	float theta;

	dtmf_detect_state->hit1 = dtmf_detect_state->hit2 = 0;

	for (i = 0;	 i < GRID_FACTOR;  i++) {
		theta = (float)(M_TWO_PI*(dtmf_row[i]/(float)sample_rate));
		dtmf_detect_row[i].fac = (float)(2.0*cos(theta));

		theta = (float)(M_TWO_PI*(dtmf_col[i]/(float)sample_rate));
		dtmf_detect_col[i].fac = (float)(2.0*cos(theta));
	
		theta = (float)(M_TWO_PI*(dtmf_row[i]*2.0/(float)sample_rate));
		dtmf_detect_row_2nd[i].fac = (float)(2.0*cos(theta));

		theta = (float)(M_TWO_PI*(dtmf_col[i]*2.0/(float)sample_rate));
		dtmf_detect_col_2nd[i].fac = (float)(2.0*cos(theta));
	
		goertzel_init (&dtmf_detect_state->row_out[i], &dtmf_detect_row[i]);
		goertzel_init (&dtmf_detect_state->col_out[i], &dtmf_detect_col[i]);
		goertzel_init (&dtmf_detect_state->row_out2nd[i], &dtmf_detect_row_2nd[i]);
		goertzel_init (&dtmf_detect_state->col_out2nd[i], &dtmf_detect_col_2nd[i]);
	
		dtmf_detect_state->energy = 0.0;
	}
	dtmf_detect_state->current_sample = 0;
	dtmf_detect_state->detected_digits = 0;
	dtmf_detect_state->lost_digits = 0;
	dtmf_detect_state->digits[0] = '\0';
	dtmf_detect_state->mhit = 0;
}

TELETONE_API(void) teletone_multi_tone_init(teletone_multi_tone_t *mt, teletone_tone_map_t *map)
{
	float theta = 0;
	int x = 0;

	if (!mt->sample_rate) {
		mt->sample_rate = 8000;
	}

	if (!mt->min_samples) {
		mt->min_samples = 102;
	}

	mt->min_samples *= (mt->sample_rate / 8000);

	if (!mt->positive_factor) {
		mt->positive_factor = 2;
	}

	if(!mt->negative_factor) {
		mt->negative_factor = 10;
	}

	if (!mt->hit_factor) {
		mt->hit_factor = 2;
	}

	for(x = 0; x < TELETONE_MAX_TONES; x++) {
		if ((int) map->freqs[x] == 0) {
			break;
		}
		mt->tone_count++;
		theta = (float)(M_TWO_PI*(map->freqs[x]/(float)mt->sample_rate));
		mt->tdd[x].fac = (float)(2.0 * cos(theta));
		goertzel_init (&mt->gs[x], &mt->tdd[x]);
		goertzel_init (&mt->gs2[x], &mt->tdd[x]);
	}

}

TELETONE_API(int) teletone_multi_tone_detect (teletone_multi_tone_t *mt,
								int16_t sample_buffer[],
								int samples)
{
	int sample, limit = 0, j, x = 0;
	float v1, famp;
	float eng_sum = 0, eng_all[TELETONE_MAX_TONES] = {0.0};
	int gtest = 0, see_hit = 0;

	for (sample = 0;  sample >= 0 && sample < samples; sample = limit) {
		mt->total_samples++;

		if ((samples - sample) >= (mt->min_samples - mt->current_sample)) {
			limit = sample + (mt->min_samples - mt->current_sample);
		} else {
			limit = samples;
		}
		if (limit < 0 || limit > samples) {
			limit = samples;
		}

		for (j = sample;  j < limit;  j++) {
			famp = sample_buffer[j];
			
			mt->energy += famp*famp;

			for(x = 0; x < TELETONE_MAX_TONES && x < mt->tone_count; x++) {
				v1 = mt->gs[x].v2;
				mt->gs[x].v2 = mt->gs[x].v3;
				mt->gs[x].v3 = (float)(mt->gs[x].fac * mt->gs[x].v2 - v1 + famp);
	
				v1 = mt->gs2[x].v2;
				mt->gs2[x].v2 = mt->gs2[x].v3;
				mt->gs2[x].v3 = (float)(mt->gs2[x].fac*mt->gs2[x].v2 - v1 + famp);
			}
		}

		mt->current_sample += (limit - sample);
		if (mt->current_sample < mt->min_samples) {
			continue;
		}

		eng_sum = 0;
		for(x = 0; x < TELETONE_MAX_TONES && x < mt->tone_count; x++) {
			eng_all[x] = (float)(teletone_goertzel_result (&mt->gs[x]));
			eng_sum += eng_all[x];
		}

		gtest = 0;
		for(x = 0; x < TELETONE_MAX_TONES && x < mt->tone_count; x++) {
			gtest += teletone_goertzel_result (&mt->gs2[x]) < eng_all[x] ? 1 : 0;
		}

		if ((gtest >= 2 || gtest == mt->tone_count) && eng_sum > 42.0 * mt->energy) {
			if(mt->negatives) {
				mt->negatives--;
			}
			mt->positives++;

			if(mt->positives >= mt->positive_factor) {
				mt->hits++;
			}
			if (mt->hits >= mt->hit_factor) {
				see_hit++;
				mt->positives = mt->negatives = mt->hits = 0;
			}
		} else {
			mt->negatives++;
			if(mt->positives) {
				mt->positives--;
			}
			if(mt->negatives > mt->negative_factor) {
				mt->positives = mt->hits = 0;
			}
		}

		/* Reinitialise the detector for the next block */
		for(x = 0; x < TELETONE_MAX_TONES && x < mt->tone_count; x++) {
			goertzel_init (&mt->gs[x], &mt->tdd[x]);
			goertzel_init (&mt->gs2[x], &mt->tdd[x]);
		}

		mt->energy = 0.0;
		mt->current_sample = 0;
	}

	return see_hit;
}


TELETONE_API(int) teletone_dtmf_detect (teletone_dtmf_detect_state_t *dtmf_detect_state,
						  int16_t sample_buffer[],
						  int samples)
{
	float row_energy[GRID_FACTOR];
	float col_energy[GRID_FACTOR];
	float famp;
	float v1;
	int i;
	int j;
	int sample;
	int best_row;
	int best_col;
	char hit;
	int limit;

	hit = 0;
	for (sample = 0;  sample < samples;	 sample = limit) {
		/* BLOCK_LEN is optimised to meet the DTMF specs. */
		if ((samples - sample) >= (BLOCK_LEN - dtmf_detect_state->current_sample)) {
			limit = sample + (BLOCK_LEN - dtmf_detect_state->current_sample);
		} else {
			limit = samples;
		}

		for (j = sample;  j < limit;  j++) {
			int x = 0;
			famp = sample_buffer[j];
			
			dtmf_detect_state->energy += famp*famp;

			for(x = 0; x < GRID_FACTOR; x++) {
				v1 = dtmf_detect_state->row_out[x].v2;
				dtmf_detect_state->row_out[x].v2 = dtmf_detect_state->row_out[x].v3;
				dtmf_detect_state->row_out[x].v3 = (float)(dtmf_detect_state->row_out[x].fac*dtmf_detect_state->row_out[x].v2 - v1 + famp);
	
				v1 = dtmf_detect_state->col_out[x].v2;
				dtmf_detect_state->col_out[x].v2 = dtmf_detect_state->col_out[x].v3;
				dtmf_detect_state->col_out[x].v3 = (float)(dtmf_detect_state->col_out[x].fac*dtmf_detect_state->col_out[x].v2 - v1 + famp);

				v1 = dtmf_detect_state->col_out2nd[x].v2;
				dtmf_detect_state->col_out2nd[x].v2 = dtmf_detect_state->col_out2nd[x].v3;
				dtmf_detect_state->col_out2nd[x].v3 = (float)(dtmf_detect_state->col_out2nd[x].fac*dtmf_detect_state->col_out2nd[x].v2 - v1 + famp);
		
				v1 = dtmf_detect_state->row_out2nd[x].v2;
				dtmf_detect_state->row_out2nd[x].v2 = dtmf_detect_state->row_out2nd[x].v3;
				dtmf_detect_state->row_out2nd[x].v3 = (float)(dtmf_detect_state->row_out2nd[x].fac*dtmf_detect_state->row_out2nd[x].v2 - v1 + famp);
			}

		}

		dtmf_detect_state->current_sample += (limit - sample);
		if (dtmf_detect_state->current_sample < BLOCK_LEN) {
			continue;
		}
		/* We are at the end of a DTMF detection block */
		/* Find the peak row and the peak column */
		row_energy[0] = teletone_goertzel_result (&dtmf_detect_state->row_out[0]);
		col_energy[0] = teletone_goertzel_result (&dtmf_detect_state->col_out[0]);

		for (best_row = best_col = 0, i = 1;  i < GRID_FACTOR;	i++) {
			row_energy[i] = teletone_goertzel_result (&dtmf_detect_state->row_out[i]);
			if (row_energy[i] > row_energy[best_row]) {
				best_row = i;
			}
			col_energy[i] = teletone_goertzel_result (&dtmf_detect_state->col_out[i]);
			if (col_energy[i] > col_energy[best_col]) {
				best_col = i;
			}
		}
		hit = 0;
		/* Basic signal level test and the twist test */
		if (row_energy[best_row] >= DTMF_THRESHOLD &&
			col_energy[best_col] >= DTMF_THRESHOLD &&
			col_energy[best_col] < row_energy[best_row]*DTMF_REVERSE_TWIST &&
			col_energy[best_col]*DTMF_NORMAL_TWIST > row_energy[best_row]) {
			/* Relative peak test */
			for (i = 0;	 i < GRID_FACTOR;  i++) {
				if ((i != best_col	&&	col_energy[i]*DTMF_RELATIVE_PEAK_COL > col_energy[best_col]) ||
					(i != best_row	&&	row_energy[i]*DTMF_RELATIVE_PEAK_ROW > row_energy[best_row])) {
					break;
				}
			}
			/* ... and second harmonic test */
			if (i >= GRID_FACTOR && (row_energy[best_row] + col_energy[best_col]) > 42.0*dtmf_detect_state->energy &&
				teletone_goertzel_result (&dtmf_detect_state->col_out2nd[best_col])*DTMF_2ND_HARMONIC_COL < col_energy[best_col] &&
				teletone_goertzel_result (&dtmf_detect_state->row_out2nd[best_row])*DTMF_2ND_HARMONIC_ROW < row_energy[best_row]) {
				hit = dtmf_positions[(best_row << 2) + best_col];
				/* Look for two successive similar results */
				/* The logic in the next test is:
				   We need two successive identical clean detects, with
				   something different preceeding it. This can work with
				   back to back differing digits. More importantly, it
				   can work with nasty phones that give a very wobbly start
				   to a digit. */
				if (hit == dtmf_detect_state->hit3	&&	dtmf_detect_state->hit3 != dtmf_detect_state->hit2) {
					dtmf_detect_state->mhit = hit;
					dtmf_detect_state->digit_hits[(best_row << 2) + best_col]++;
					dtmf_detect_state->detected_digits++;
					if (dtmf_detect_state->current_digits < TELETONE_MAX_DTMF_DIGITS) {
						dtmf_detect_state->digits[dtmf_detect_state->current_digits++] = hit;
						dtmf_detect_state->digits[dtmf_detect_state->current_digits] = '\0';
					}
					else
						{
							dtmf_detect_state->lost_digits++;
						}
				}
			}
		}
		dtmf_detect_state->hit1 = dtmf_detect_state->hit2;
		dtmf_detect_state->hit2 = dtmf_detect_state->hit3;
		dtmf_detect_state->hit3 = hit;
		/* Reinitialise the detector for the next block */
		for (i = 0;	 i < GRID_FACTOR;  i++) {
			goertzel_init (&dtmf_detect_state->row_out[i], &dtmf_detect_row[i]);
			goertzel_init (&dtmf_detect_state->col_out[i], &dtmf_detect_col[i]);
			goertzel_init (&dtmf_detect_state->row_out2nd[i], &dtmf_detect_row_2nd[i]);
			goertzel_init (&dtmf_detect_state->col_out2nd[i], &dtmf_detect_col_2nd[i]);
		}
		dtmf_detect_state->energy = 0.0;
		dtmf_detect_state->current_sample = 0;
	}
	if ((!dtmf_detect_state->mhit) || (dtmf_detect_state->mhit != hit)) {
		dtmf_detect_state->mhit = 0;
		return(0);
	}
	return (hit);
}


TELETONE_API(int) teletone_dtmf_get (teletone_dtmf_detect_state_t *dtmf_detect_state,
					   char *buf,
					   int max)
{
	teletone_assert(dtmf_detect_state->current_digits <= TELETONE_MAX_DTMF_DIGITS);

	if (max > dtmf_detect_state->current_digits) {
		max = dtmf_detect_state->current_digits;
	}
	if (max > 0) {
		memcpy (buf, dtmf_detect_state->digits, max);
		memmove (dtmf_detect_state->digits, dtmf_detect_state->digits + max, dtmf_detect_state->current_digits - max);
		dtmf_detect_state->current_digits -= max;
	}
	buf[max] = '\0';
	return	max;
}

/* For Emacs:
 * Local Variables:
 * mode:c
 * indent-tabs-mode:t
 * tab-width:4
 * c-basic-offset:4
 * End:
 * For VIM:
 * vim:set softtabstop=4 shiftwidth=4 tabstop=4:
 */
