/* $Id$ */
/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */
#include <pjmedia/tonegen.h>
#include <pjmedia/errno.h>
#include <pjmedia/silencedet.h>
#include <pj/assert.h>
#include <pj/ctype.h>
#include <pj/lock.h>
#include <pj/log.h>
#include <pj/pool.h>

/* amplitude */
#define AMP	PJMEDIA_TONEGEN_VOLUME

#ifndef M_PI
#   define M_PI  ((DATA)3.141592653589793238462643383279)
#endif

#if PJMEDIA_TONEGEN_ALG==PJMEDIA_TONEGEN_SINE
    #include <math.h>
    #define DATA	double

    /*
     * This is the good old tone generator using sin().
     * Speed = 1347 usec to generate 1 second, 8KHz dual-tones (2.66GHz P4).
     *         approx. 10.91 MIPS
     *
     *         506,535 usec/100.29 MIPS on ARM926EJ-S.
     */
    struct gen
    {
	DATA add;
	DATA c;
	DATA vol;
    };

    #define GEN_INIT(var,R,F,A) var.add = ((DATA)F)/R, var.c=0, var.vol=A
    #define GEN_SAMP(val,var)   val = (short)(sin(var.c * 2 * M_PI) * \
					      var.vol); \
			        var.c += var.add

#elif PJMEDIA_TONEGEN_ALG==PJMEDIA_TONEGEN_FLOATING_POINT
    #include <math.h>
    #define DATA	float

    /*
     * Default floating-point based tone generation using sine wave 
     * generation from:
     *   http://www.musicdsp.org/showone.php?id=10.
     * This produces good quality tone in relatively faster time than
     * the normal sin() generator.
     * Speed = 350 usec to generate 1 second, 8KHz dual-tones (2.66GHz P4).
     *         approx. 2.84 MIPS
     *
     *         18,037 usec/3.57 MIPS on ARM926EJ-S.
     */
    struct gen
    {
	DATA a, s0, s1;
    };

    #define GEN_INIT(var,R,F,A) var.a = (DATA) (2.0 * sin(M_PI * F / R)); \
			        var.s0 = 0; \
			        var.s1 = (DATA)(0 - (int)A)
    #define GEN_SAMP(val,var)   var.s0 = var.s0 - var.a * var.s1; \
			        var.s1 = var.s1 + var.a * var.s0; \
			        val = (short) var.s0

#elif PJMEDIA_TONEGEN_ALG==PJMEDIA_TONEGEN_FIXED_POINT_CORDIC
    /* Cordic algorithm with 28 bit size, from:
     * http://www.dcs.gla.ac.uk/~jhw/cordic/
     * Speed = 742 usec to generate 1 second, 8KHz dual-tones (2.66GHz P4).
     *         (PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP=7)
     *         approx. 6.01 MIPS
     *
     *         ARM926EJ-S results:
     *	        loop=7:   8,943 usec/1.77 MIPS
     *		loop=8:   9,872 usec/1.95 MIPS
     *          loop=10: 11,662 usec/2.31 MIPS
     *          loop=12: 13,561 usec/2.69 MIPS
     */
    #define CORDIC_1K		0x026DD3B6
    #define CORDIC_HALF_PI	0x06487ED5
    #define CORDIC_PI		(CORDIC_HALF_PI * 2)
    #define CORDIC_MUL_BITS	26
    #define CORDIC_MUL		(1 << CORDIC_MUL_BITS)
    #define CORDIC_NTAB		28
    #define CORDIC_LOOP		PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP

    static int cordic_ctab [] = 
    {
	0x03243F6A, 0x01DAC670, 0x00FADBAF, 0x007F56EA, 0x003FEAB7, 
	0x001FFD55, 0x000FFFAA, 0x0007FFF5, 0x0003FFFE, 0x0001FFFF, 
	0x0000FFFF, 0x00007FFF, 0x00003FFF, 0x00001FFF, 0x00000FFF, 
	0x000007FF, 0x000003FF, 0x000001FF, 0x000000FF, 0x0000007F, 
	0x0000003F, 0x0000001F, 0x0000000F, 0x00000007, 0x00000003, 
	0x00000001, 0x00000000, 0x00000000 
    };

    static pj_int32_t cordic(pj_int32_t theta, unsigned n)
    {
	unsigned k;
	int d;
	pj_int32_t tx;
	pj_int32_t x = CORDIC_1K, y = 0, z = theta;

	for (k=0; k<n; ++k) {
	    #if 0
	    d = (z>=0) ? 0 : -1;
	    #else
	    /* Only slightly (~2.5%) faster, but not portable? */
	     d = z>>27;
	    #endif
	    tx = x - (((y>>k) ^ d) - d);
	    y = y + (((x>>k) ^ d) - d);
	    z = z - ((cordic_ctab[k] ^ d) - d);
	    x = tx;
	}  
	return y;
    }

    /* Note: theta must be uint32 here */
    static pj_int32_t cordic_sin(pj_uint32_t theta, unsigned n)
    {
	if (theta < CORDIC_HALF_PI)
	    return cordic(theta, n);
	else if (theta < CORDIC_PI)
	    return cordic(CORDIC_HALF_PI-(theta-CORDIC_HALF_PI), n);
	else if (theta < CORDIC_PI + CORDIC_HALF_PI)
	    return -cordic(theta - CORDIC_PI, n);
	else if (theta < 2 * CORDIC_PI)
	    return -cordic(CORDIC_HALF_PI-(theta-3*CORDIC_HALF_PI), n);
	else {
	    pj_assert(!"Invalid cordic_sin() value");
	    return 0;
	}
    }

    struct gen
    {
	unsigned    add;
	pj_uint32_t c;
	unsigned    vol;
    };

    #define VOL(var,v)		(((v) * var.vol) >> 15)
    #define GEN_INIT(var,R,F,A)	gen_init(&var, R, F, A)
    #define GEN_SAMP(val,var)	val = gen_samp(&var)

    static void gen_init(struct gen *var, unsigned R, unsigned F, unsigned A)
    {
	var->add = 2*CORDIC_PI/R * F;
	var->c = 0;
	var->vol = A;
    }

    PJ_INLINE(short) gen_samp(struct gen *var)
    {
	pj_int32_t val;
	val = cordic_sin(var->c, CORDIC_LOOP);
	/*val = (val * 32767) / CORDIC_MUL;
	 *val = VOL((*var), val);
	 */
	val = ((val >> 10) * var->vol) >> 16;
	var->c += var->add;
	if (var->c > 2*CORDIC_PI)
	    var->c -= (2 * CORDIC_PI);
	return (short) val;
    }

#elif PJMEDIA_TONEGEN_ALG==PJMEDIA_TONEGEN_FAST_FIXED_POINT

    /* 
     * Fallback algorithm when floating point is disabled.
     * This is a very fast fixed point tone generation using sine wave
     * approximation from
     *    http://www.audiomulch.com/~rossb/code/sinusoids/ 
     * Quality wise not so good, but it's blazing fast!
     * Speed = 117 usec to generate 1 second, 8KHz dual-tones (2.66GHz P4).
     *         approx. 0.95 MIPS
     *
     *         1,449 usec/0.29 MIPS on ARM926EJ-S.
     */
    PJ_INLINE(int) approximate_sin3(unsigned x)
    {	
	    unsigned s=-(int)(x>>31);
	    x+=x;
	    x=x>>16;
	    x*=x^0xffff;            // x=x*(2-x)
	    x+=x;                   // optional
	    return x^s;
    }
    struct gen
    {
	unsigned add;
	unsigned c;
	unsigned vol;
    };

    #define MAXI		((unsigned)0xFFFFFFFF)
    #define SIN			approximate_sin3
    #define VOL(var,v)		(((v) * var.vol) >> 15)
    #define GEN_INIT(var,R,F,A)	var.add = MAXI/R * F, var.c=0, var.vol=A
    #define GEN_SAMP(val,var)	val = (short) VOL(var,SIN(var.c)>>16); \
				var.c += var.add

#else
    #error "PJMEDIA_TONEGEN_ALG is not set correctly"
#endif

struct gen_state
{
    struct gen tone1;
    struct gen tone2;
    pj_bool_t  has_tone2;
};


static void init_generate_single_tone(struct gen_state *state,
				      unsigned clock_rate, 
				      unsigned freq,
				      unsigned vol)
{
    GEN_INIT(state->tone1,clock_rate,freq,vol);
    state->has_tone2 = PJ_FALSE;
}

static void generate_single_tone(struct gen_state *state,
				 unsigned channel_count,
				 unsigned samples,
				 short buf[]) 
{
    short *end = buf + samples;

    if (channel_count==1) {

	while (buf < end) {
	    GEN_SAMP(*buf++, state->tone1);
	}

    } else if (channel_count == 2) {

	while (buf < end) {
	    GEN_SAMP(*buf, state->tone1);
	    *(buf+1) = *buf;
	    buf += 2;
	}
    }
}


static void init_generate_dual_tone(struct gen_state *state,
				    unsigned clock_rate, 
				    unsigned freq1,
				    unsigned freq2,
				    unsigned vol)
{
    GEN_INIT(state->tone1,clock_rate,freq1,vol);
    GEN_INIT(state->tone2,clock_rate,freq2,vol);
    state->has_tone2 = PJ_TRUE;
}


static void generate_dual_tone(struct gen_state *state,
			       unsigned channel_count,
			       unsigned samples,
			       short buf[]) 
{
    short *end = buf + samples;

    if (channel_count==1) {
	int val, val2;
	while (buf < end) {
	    GEN_SAMP(val, state->tone1);
	    GEN_SAMP(val2, state->tone2);
	    *buf++ = (short)((val+val2) >> 1);
	}
    } else if (channel_count == 2) {
	int val, val2;
	while (buf < end) {

	    GEN_SAMP(val, state->tone1);
	    GEN_SAMP(val2, state->tone2);
	    val = (val + val2) >> 1;

	    *buf++ = (short)val;
	    *buf++ = (short)val;
	}
    }
}


static void init_generate_tone(struct gen_state *state,
			       unsigned clock_rate, 
			       unsigned freq1,
			       unsigned freq2,
			       unsigned vol)
{
    if (freq2)
	init_generate_dual_tone(state, clock_rate, freq1, freq2 ,vol);
    else
	init_generate_single_tone(state, clock_rate, freq1,vol);
}


static void generate_tone(struct gen_state *state,
			  unsigned channel_count,
			  unsigned samples,
			  short buf[])
{
    if (!state->has_tone2)
	generate_single_tone(state, channel_count, samples, buf);
    else
	generate_dual_tone(state, channel_count, samples, buf);
}


/****************************************************************************/

#define SIGNATURE   PJMEDIA_SIG_PORT_TONEGEN
#define THIS_FILE   "tonegen.c"

#if 0
#   define TRACE_(expr)	PJ_LOG(4,expr)
#else
#   define TRACE_(expr)
#endif

enum flags
{
    PJMEDIA_TONE_INITIALIZED	= 1,
    PJMEDIA_TONE_ENABLE_FADE	= 2
};

struct tonegen
{
    pjmedia_port	base;

    /* options */
    unsigned		options;
    unsigned		playback_options;
    unsigned		fade_in_len;	/* fade in for this # of samples */
    unsigned		fade_out_len;	/* fade out for this # of samples*/

    /* lock */
    pj_lock_t	       *lock;

    /* Digit map */
    pjmedia_tone_digit_map  *digit_map;

    /* Tone generation state */
    struct gen_state	state;

    /* Currently played digits: */
    unsigned		count;		    /* # of digits		*/
    unsigned		cur_digit;	    /* currently played		*/
    unsigned		dig_samples;	    /* sample pos in cur digit	*/
    pjmedia_tone_desc	digits[PJMEDIA_TONEGEN_MAX_DIGITS];/* array of digits*/
};


/* Default digit map is DTMF */
static pjmedia_tone_digit_map digit_map = 
{
    16,
    {
	{ '0', 941,  1336 },
	{ '1', 697,  1209 },
	{ '2', 697,  1336 },
	{ '3', 697,  1477 },
	{ '4', 770,  1209 },
	{ '5', 770,  1336 },
	{ '6', 770,  1477 },
	{ '7', 852,  1209 },
	{ '8', 852,  1336 },
	{ '9', 852,  1477 },
	{ 'a', 697,  1633 },
	{ 'b', 770,  1633 },
	{ 'c', 852,  1633 },
	{ 'd', 941,  1633 },
	{ '*', 941,  1209 },
	{ '#', 941,  1477 },
    }
};


static pj_status_t tonegen_get_frame(pjmedia_port *this_port, 
				     pjmedia_frame *frame);
static pj_status_t tonegen_destroy(pjmedia_port *this_port);

/*
 * Create an instance of tone generator with the specified parameters.
 * When the tone generator is first created, it will be loaded with the
 * default digit map.
 */
PJ_DEF(pj_status_t) pjmedia_tonegen_create2(pj_pool_t *pool,
					    const pj_str_t *name,
					    unsigned clock_rate,
					    unsigned channel_count,
					    unsigned samples_per_frame,
					    unsigned bits_per_sample,
					    unsigned options,
					    pjmedia_port **p_port)
{
    const pj_str_t STR_TONE_GEN = pj_str("tonegen");
    struct tonegen  *tonegen;
    pj_status_t status;

    PJ_ASSERT_RETURN(pool && clock_rate && channel_count && 
		     samples_per_frame && bits_per_sample == 16 && 
		     p_port != NULL, PJ_EINVAL);

    /* Only support mono and stereo */
    PJ_ASSERT_RETURN(channel_count==1 || channel_count==2, PJ_EINVAL);

    /* Create and initialize port */
    tonegen = PJ_POOL_ZALLOC_T(pool, struct tonegen);
    if (name == NULL || name->slen == 0) name = &STR_TONE_GEN;
    status = pjmedia_port_info_init(&tonegen->base.info, name, 
				    SIGNATURE, clock_rate, channel_count, 
				    bits_per_sample, samples_per_frame);
    if (status != PJ_SUCCESS)
	return status;

    tonegen->options = options;
    tonegen->base.get_frame = &tonegen_get_frame;
    tonegen->base.on_destroy = &tonegen_destroy;
    tonegen->digit_map = &digit_map;

    tonegen->fade_in_len = PJMEDIA_TONEGEN_FADE_IN_TIME * clock_rate / 1000;
    tonegen->fade_out_len = PJMEDIA_TONEGEN_FADE_OUT_TIME * clock_rate / 1000;

    /* Lock */
    if (options & PJMEDIA_TONEGEN_NO_LOCK) {
	status = pj_lock_create_null_mutex(pool, "tonegen", &tonegen->lock);
    } else {
	status = pj_lock_create_simple_mutex(pool, "tonegen", &tonegen->lock);
    }

    if (status != PJ_SUCCESS) {
	return status;
    }

    TRACE_((THIS_FILE, "Tonegen created: %u/%u/%u/%u", clock_rate, 
	    channel_count, samples_per_frame, bits_per_sample));

    /* Done */
    *p_port = &tonegen->base;
    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pjmedia_tonegen_create( pj_pool_t *pool,
					    unsigned clock_rate,
					    unsigned channel_count,
					    unsigned samples_per_frame,
					    unsigned bits_per_sample,
					    unsigned options,
					    pjmedia_port **p_port)
{
    return pjmedia_tonegen_create2(pool, NULL, clock_rate, channel_count,
				   samples_per_frame, bits_per_sample, 
				   options, p_port);
}


/*
 * Check if the tone generator is still busy producing some tones.
 */
PJ_DEF(pj_bool_t) pjmedia_tonegen_is_busy(pjmedia_port *port)
{
    struct tonegen *tonegen = (struct tonegen*) port;
    PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_TRUE);
    return tonegen->count != 0;
}


/*
 * Instruct the tone generator to stop current processing.
 */
PJ_DEF(pj_status_t) pjmedia_tonegen_stop(pjmedia_port *port)
{
    struct tonegen *tonegen = (struct tonegen*) port;
    PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);

    TRACE_((THIS_FILE, "tonegen_stop()"));

    pj_lock_acquire(tonegen->lock);
    tonegen->count = 0;
    tonegen->cur_digit = 0;
    tonegen->dig_samples = 0;
    pj_lock_release(tonegen->lock);

    return PJ_SUCCESS;
}

/*
 * Instruct the tone generator to stop looping of the current tone set.
 */
PJ_DEF(pj_status_t) pjmedia_tonegen_stop_loop(pjmedia_port *port)
{
    struct tonegen *tonegen = (struct tonegen*) port;
    PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);

    TRACE_((THIS_FILE, "tonegen_stop_loop()"));

    pj_lock_acquire(tonegen->lock);
    tonegen->playback_options &= ~PJMEDIA_TONEGEN_LOOP;
    pj_lock_release(tonegen->lock);

    return PJ_SUCCESS;
}

/*
 * Instruct the tone generator to stop current processing.
 */
PJ_DEF(pj_status_t) pjmedia_tonegen_rewind(pjmedia_port *port)
{
    struct tonegen *tonegen = (struct tonegen*) port;
    PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);

    TRACE_((THIS_FILE, "tonegen_rewind()"));

    /* Reset back to the first tone */
    pj_lock_acquire(tonegen->lock);
    tonegen->cur_digit = 0;
    tonegen->dig_samples = 0;
    pj_lock_release(tonegen->lock);

    return PJ_SUCCESS;
}


/*
 * Callback to destroy tonegen
 */
static pj_status_t tonegen_destroy(pjmedia_port *port)
{
    struct tonegen *tonegen = (struct tonegen*) port;
    PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);

    TRACE_((THIS_FILE, "tonegen_destroy()"));

    pj_lock_acquire(tonegen->lock);
    pj_lock_release(tonegen->lock);

    pj_lock_destroy(tonegen->lock);

    return PJ_SUCCESS;
}

/*
 * Fill a frame with tones.
 */
static pj_status_t tonegen_get_frame(pjmedia_port *port, 
				     pjmedia_frame *frame)
{
    struct tonegen *tonegen = (struct tonegen*) port;
    short *dst, *end;
    unsigned clock_rate = PJMEDIA_PIA_SRATE(&tonegen->base.info);

    PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);

    pj_lock_acquire(tonegen->lock);

    if (tonegen->count == 0) {
	/* We don't have digits to play */
	frame->type = PJMEDIA_FRAME_TYPE_NONE;
	goto on_return;
    }

    if (tonegen->cur_digit > tonegen->count) {
	/* We have played all the digits */
	if ((tonegen->options|tonegen->playback_options)&PJMEDIA_TONEGEN_LOOP)
	{
	    /* Reset back to the first tone */
	    tonegen->cur_digit = 0;
	    tonegen->dig_samples = 0;

	    TRACE_((THIS_FILE, "tonegen_get_frame(): rewind"));

	} else {
	    tonegen->count = 0;
	    tonegen->cur_digit = 0;
	    frame->type = PJMEDIA_FRAME_TYPE_NONE;
	    TRACE_((THIS_FILE, "tonegen_get_frame(): no more digit"));
	    goto on_return;
	}
    }

    if (tonegen->dig_samples>=(tonegen->digits[tonegen->cur_digit].on_msec+
			       tonegen->digits[tonegen->cur_digit].off_msec)*
			       clock_rate / 1000)
    {
	/* We have finished with current digit */
	tonegen->cur_digit++;
	tonegen->dig_samples = 0;

	TRACE_((THIS_FILE, "tonegen_get_frame(): next digit"));
    }

    if (tonegen->cur_digit >= tonegen->count) {
	/* After we're finished with the last digit, we have played all 
	 * the digits 
	 */
	if ((tonegen->options|tonegen->playback_options)&PJMEDIA_TONEGEN_LOOP)
	{
	    /* Reset back to the first tone */
	    tonegen->cur_digit = 0;
	    tonegen->dig_samples = 0;

	    TRACE_((THIS_FILE, "tonegen_get_frame(): rewind"));

	} else {
	    tonegen->count = 0;
	    tonegen->cur_digit = 0;
	    frame->type = PJMEDIA_FRAME_TYPE_NONE;
	    TRACE_((THIS_FILE, "tonegen_get_frame(): no more digit"));
	    goto on_return;
	}
    }
    
    dst = (short*) frame->buf;
    end = dst + PJMEDIA_PIA_SPF(&port->info);

    while (dst < end) {
	pjmedia_tone_desc *dig = &tonegen->digits[tonegen->cur_digit];
	unsigned required, cnt, on_samp, off_samp;

	required = (unsigned)(end - dst);
	on_samp = dig->on_msec * clock_rate / 1000;
	off_samp = dig->off_msec * clock_rate / 1000;

	/* Init tonegen */
	if (tonegen->dig_samples == 0 && 
	    (tonegen->count!=1 || !(dig->flags & PJMEDIA_TONE_INITIALIZED)))
	{
	    init_generate_tone(&tonegen->state,
		               PJMEDIA_PIA_SRATE(&port->info),
			       dig->freq1, dig->freq2, dig->volume);
	    dig->flags |= PJMEDIA_TONE_INITIALIZED;
	    if (tonegen->cur_digit > 0) {
		/* Clear initialized flag of previous digit */
		tonegen->digits[tonegen->cur_digit-1].flags &= 
						(~PJMEDIA_TONE_INITIALIZED);
	    }
	}

	/* Add tone signal */
	if (tonegen->dig_samples < on_samp) {
	    cnt = on_samp - tonegen->dig_samples;
	    if (cnt > required)
		cnt = required;
	    generate_tone(&tonegen->state,
			  PJMEDIA_PIA_CCNT(&port->info),
			  cnt, dst);

	    dst += cnt;
	    tonegen->dig_samples += cnt;
	    required -= cnt;

	    if ((dig->flags & PJMEDIA_TONE_ENABLE_FADE) && 
		tonegen->dig_samples == cnt) 
	    {
		/* Fade in */
		short *samp = (dst - cnt);
		short *samp_end;

		if (cnt > tonegen->fade_in_len)
		    cnt = tonegen->fade_in_len;
		samp_end = samp + cnt;
		if (cnt) {
		    const unsigned step = 0xFFFF / cnt;
		    unsigned scale = 0;

		    for (; samp < samp_end; ++samp) {
			(*samp) = (short)(((*samp) * scale) >> 16);
			scale += step;
		    }
		}
	    } else if ((dig->flags & PJMEDIA_TONE_ENABLE_FADE) &&
			tonegen->dig_samples==on_samp) 
	    {
		/* Fade out */
		if (cnt > tonegen->fade_out_len)
		    cnt = tonegen->fade_out_len;
		if (cnt) {
		    short *samp = (dst - cnt);
		    const unsigned step = 0xFFFF / cnt;
		    unsigned scale = 0xFFFF - step;

		    for (; samp < dst; ++samp) {
			(*samp) = (short)(((*samp) * scale) >> 16);
			scale -= step;
		    }
		}
	    }

	    if (dst == end)
		break;
	}

	/* Add silence signal */
	cnt = off_samp + on_samp - tonegen->dig_samples;
	if (cnt > required)
	    cnt = required;
	pjmedia_zero_samples(dst, cnt);
	dst += cnt;
	tonegen->dig_samples += cnt;

	/* Move to next digit if we're finished with this tone */
	if (tonegen->dig_samples >= on_samp + off_samp) {
	    tonegen->cur_digit++;
	    tonegen->dig_samples = 0;

	    if (tonegen->cur_digit >= tonegen->count) {
		/* All digits have been played */
		if ((tonegen->options & PJMEDIA_TONEGEN_LOOP) ||
		    (tonegen->playback_options & PJMEDIA_TONEGEN_LOOP))
		{
		    tonegen->cur_digit = 0;
		} else {
		    break;
		}
	    }
	}
    }

    if (dst < end)
	pjmedia_zero_samples(dst, (unsigned)(end-dst));

    frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
    frame->size = PJMEDIA_PIA_AVG_FSZ(&port->info);

    TRACE_((THIS_FILE, "tonegen_get_frame(): frame created, level=%u",
	    pjmedia_calc_avg_signal((pj_int16_t*)frame->buf, frame->size/2)));

    if (tonegen->cur_digit >= tonegen->count) {
	if ((tonegen->options|tonegen->playback_options)&PJMEDIA_TONEGEN_LOOP)
	{
	    /* Reset back to the first tone */
	    tonegen->cur_digit = 0;
	    tonegen->dig_samples = 0;

	    TRACE_((THIS_FILE, "tonegen_get_frame(): rewind"));

	} else {
	    tonegen->count = 0;
	    tonegen->cur_digit = 0;

	    TRACE_((THIS_FILE, "tonegen_get_frame(): no more digit"));
	}
    }

on_return:
    pj_lock_release(tonegen->lock);
    return PJ_SUCCESS;
}


/*
 * Play tones.
 */
PJ_DEF(pj_status_t) pjmedia_tonegen_play( pjmedia_port *port,
					  unsigned count,
					  const pjmedia_tone_desc tones[],
					  unsigned options)
{
    struct tonegen *tonegen = (struct tonegen*) port;
    unsigned i;

    PJ_ASSERT_RETURN(port && port->info.signature == SIGNATURE &&
		     count && tones, PJ_EINVAL);

    /* Don't put more than available buffer */
    PJ_ASSERT_RETURN(count+tonegen->count <= PJMEDIA_TONEGEN_MAX_DIGITS,
		     PJ_ETOOMANY);

    pj_lock_acquire(tonegen->lock);

    /* Set playback options */
    tonegen->playback_options = options;
    
    /* Copy digits */
    pj_memcpy(tonegen->digits + tonegen->count,
	      tones, count * sizeof(pjmedia_tone_desc));
    
    /* Normalize volume, and check if we need to disable fading.
     * Disable fading if tone off time is zero. Application probably
     * wants to play this tone continuously (e.g. dial tone).
     */
    for (i=0; i<count; ++i) {
	pjmedia_tone_desc *t = &tonegen->digits[i+tonegen->count];
	if (t->volume == 0)
	    t->volume = AMP;
	else if (t->volume < 0)
	    t->volume = (short) -t->volume;
	/* Reset flags */
	t->flags = 0;
	if (t->off_msec != 0)
	    t->flags |= PJMEDIA_TONE_ENABLE_FADE;
    }

    tonegen->count += count;

    pj_lock_release(tonegen->lock);

    return PJ_SUCCESS;
}


/*
 * Play digits.
 */
PJ_DEF(pj_status_t) pjmedia_tonegen_play_digits( pjmedia_port *port,
						 unsigned count,
						 const pjmedia_tone_digit digits[],
						 unsigned options)
{
    struct tonegen *tonegen = (struct tonegen*) port;
    pjmedia_tone_desc tones[PJMEDIA_TONEGEN_MAX_DIGITS];
    const pjmedia_tone_digit_map *map;
    unsigned i;

    PJ_ASSERT_RETURN(port && port->info.signature == SIGNATURE &&
		     count && digits, PJ_EINVAL);
    PJ_ASSERT_RETURN(count < PJMEDIA_TONEGEN_MAX_DIGITS, PJ_ETOOMANY);

    pj_lock_acquire(tonegen->lock);

    map = tonegen->digit_map;

    for (i=0; i<count; ++i) {
	int d = pj_tolower(digits[i].digit);
	unsigned j;

	/* Translate ASCII digits with digitmap */
	for (j=0; j<map->count; ++j) {
	    if (d == map->digits[j].digit)
		break;
	}
	if (j == map->count) {
	    pj_lock_release(tonegen->lock);
	    return PJMEDIA_RTP_EINDTMF;
	}

	tones[i].freq1 = map->digits[j].freq1;
	tones[i].freq2 = map->digits[j].freq2;
	tones[i].on_msec = digits[i].on_msec;
	tones[i].off_msec = digits[i].off_msec;
	tones[i].volume = digits[i].volume;
    }

    pj_lock_release(tonegen->lock);

    return pjmedia_tonegen_play(port, count, tones, options);
}


/*
 * Get the digit-map currently used by this tone generator.
 */
PJ_DEF(pj_status_t) pjmedia_tonegen_get_digit_map(pjmedia_port *port,
						  const pjmedia_tone_digit_map **m)
{
    struct tonegen *tonegen = (struct tonegen*) port;
    
    PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
    PJ_ASSERT_RETURN(m != NULL, PJ_EINVAL);

    *m = tonegen->digit_map;

    return PJ_SUCCESS;
}


/*
 * Set digit map to be used by the tone generator.
 */
PJ_DEF(pj_status_t) pjmedia_tonegen_set_digit_map(pjmedia_port *port,
						  pjmedia_tone_digit_map *m)
{
    struct tonegen *tonegen = (struct tonegen*) port;
    
    PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
    PJ_ASSERT_RETURN(m != NULL, PJ_EINVAL);

    pj_lock_acquire(tonegen->lock);

    tonegen->digit_map = m;

    pj_lock_release(tonegen->lock);

    return PJ_SUCCESS;
}


