Ticket #619: added configuration to force floating point algorithm to be used in the tone generator, and added fade-in and fade out options

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@2281 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjmedia/include/pjmedia/config.h b/pjmedia/include/pjmedia/config.h
index 46bf18b..e592fcf 100644
--- a/pjmedia/include/pjmedia/config.h
+++ b/pjmedia/include/pjmedia/config.h
@@ -541,10 +541,65 @@
  * Enable high quality of tone generation, the better quality will cost
  * more CPU load. This is only applied to floating point enabled machines.
  *
- * By default it is enabled.
+ * By default it is enabled when PJ_HAS_FLOATING_POINT is set.
+ *
+ * @see PJMEDIA_TONEGEN_FORCE_FLOAT
  */
 #ifndef PJMEDIA_USE_HIGH_QUALITY_TONEGEN
-#   define PJMEDIA_USE_HIGH_QUALITY_TONEGEN	    1
+#   define PJMEDIA_USE_HIGH_QUALITY_TONEGEN	    PJ_HAS_FLOATING_POINT
+#endif
+
+
+/**
+ * Force the tone generation to use floating point computation, even when
+ * PJ_HAS_FLOATING_POINT is disabled. This may be necessary if the tone
+ * generator is used to produce DTMF to be sent inband, since the fixed
+ * point algorithm may not have the correct frequency accuracy.
+ *
+ * This option, combined with PJ_HAS_FLOATING_POINT will produce the 
+ * following selection of tone generator algorithm:
+ *  - if both PJ_HAS_FLOATING_POINT and PJMEDIA_USE_HIGH_QUALITY_TONEGEN 
+ *    are set, the standard sin() function will be used. This will produce
+ *    the highest quality tones, at the expense of more processing power.
+ *  - if PJ_HAS_FLOATING_POINT is not set:
+ *	- if both PJMEDIA_USE_HIGH_QUALITY_TONEGEN and 
+ *        PJMEDIA_TONEGEN_FORCE_FLOAT are set, sin() based algorithm will
+ *        be used (similar as above).
+ *      - if PJMEDIA_USE_HIGH_QUALITY_TONEGEN is not set but the
+ *        PJMEDIA_TONEGEN_FORCE_FLOAT is set, a floating point approximation
+ *        algorithm will be used. This should produce good enough tone
+ *        for most uses, and the performance is faster than using pure
+ *        sin() based algorithm. Note that linking to math library may
+ *        still be needed.
+ *      - if both are not set, the fixed point approximation algorithm
+ *        will be used.
+ *
+ * Default: 1
+ */
+#ifndef PJMEDIA_TONEGEN_FORCE_FLOAT
+#   define PJMEDIA_TONEGEN_FORCE_FLOAT		    1
+#endif
+
+
+/**
+ * Fade-in duration for the tone, in milliseconds. Set to zero to disable
+ * this feature.
+ *
+ * Default: 1 (msec)
+ */
+#ifndef PJMEDIA_TONEGEN_FADE_IN_TIME
+#   define PJMEDIA_TONEGEN_FADE_IN_TIME		    1
+#endif
+
+
+/**
+ * Fade-out duration for the tone, in milliseconds. Set to zero to disable
+ * this feature.
+ *
+ * Default: 2 (msec)
+ */
+#ifndef PJMEDIA_TONEGEN_FADE_OUT_TIME
+#   define PJMEDIA_TONEGEN_FADE_OUT_TIME	    2
 #endif
 
 
diff --git a/pjmedia/src/pjmedia/tonegen.c b/pjmedia/src/pjmedia/tonegen.c
index c217894..562195d 100644
--- a/pjmedia/src/pjmedia/tonegen.c
+++ b/pjmedia/src/pjmedia/tonegen.c
@@ -38,7 +38,8 @@
 #endif
 
 
-#if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0
+#if (defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0) || \
+    (defined(PJMEDIA_TONEGEN_FORCE_FLOAT) && PJMEDIA_TONEGEN_FORCE_FLOAT != 0)
 #   include <math.h>
 
 #   if defined(PJMEDIA_USE_HIGH_QUALITY_TONEGEN) && \
@@ -249,6 +250,8 @@
     /* 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;
@@ -335,6 +338,9 @@
     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);
@@ -533,10 +539,44 @@
 		cnt = required;
 	    generate_tone(&tonegen->state, port->info.channel_count,
 			  cnt, dst);
+
 	    dst += cnt;
 	    tonegen->dig_samples += cnt;
 	    required -= cnt;
 
+	    if (tonegen->dig_samples == cnt) {
+		/* Fade in */
+		short *samp = (dst - cnt);
+		short *end;
+
+		if (cnt > tonegen->fade_in_len)
+		    cnt = tonegen->fade_in_len;
+		end = samp + cnt;
+		if (cnt) {
+		    const unsigned step = 0xFFFF / cnt;
+		    unsigned scale = 0;
+
+		    for (; samp < end; ++samp) {
+			(*samp) = (short)(((*samp) * scale) >> 16);
+			scale += step;
+		    }
+		}
+	    } else if (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;
 	}