/* 
 * 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;
    pj_pool_t          *pool;

    /* 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_SUCCESS;
    pj_pool_t *pool = NULL;

    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);

    if (name == NULL || name->slen == 0)
        name = &STR_TONE_GEN;

    /* Create own pool */
    pool = pj_pool_create(pool_->factory, name->ptr, 500, 500, NULL);
    PJ_ASSERT_RETURN(pool, PJ_ENOMEM);

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

    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) {
        goto on_return;
    }

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

    /* Done */
    *p_port = &tonegen->base;

on_return:
    if (status != PJ_SUCCESS) {
        if (pool)
            pj_pool_release(pool);
    }
    return status;
}


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()"));

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

        pj_lock_destroy(tonegen->lock);
        tonegen->lock = NULL;
    }

    if (tonegen->pool)
        pj_pool_safe_release(&tonegen->pool);

    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 *
                               PJMEDIA_PIA_CCNT(&port->info))
    {
        /* 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 *
                  PJMEDIA_PIA_CCNT(&port->info);
        off_samp = dig->off_msec * clock_rate / 1000 *
                   PJMEDIA_PIA_CCNT(&port->info);

        /* 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;
        tones[i].flags = 0;
    }

    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;
}


