]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
Changes to the signaling tone detector to detect concurrent 2400Hz + 2600Hz
authorSteve Underwood <steveu@coppice.org>
Sun, 6 Jun 2010 14:24:20 +0000 (22:24 +0800)
committerSteve Underwood <steveu@coppice.org>
Sun, 6 Jun 2010 14:24:20 +0000 (22:24 +0800)
tones. This passes voice immunity and other key tests, but it bounces a bit
when transitions like 2400 -> 2400+2600 -> 2600 occur. Transitions between
tone off and tone on are clean.

libs/spandsp/src/sig_tone.c
libs/spandsp/src/spandsp/private/sig_tone.h
libs/spandsp/tests/sig_tone_tests.c

index eeb50ef3cecf9fe77bb10f2c98f2e8cb3517ca39..f25119c97d3869b8f3f4619be4b45c67ed5db01e 100644 (file)
@@ -23,7 +23,7 @@
  * License along with this program; if not, write to the Free Software
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  *
- * $Id: sig_tone.c,v 1.39 2010/03/11 14:22:30 steveu Exp $
+ * $Id: sig_tone.c,v 1.40 2010/05/12 15:32:41 steveu Exp $
  */
 
 /*! \file */
@@ -201,16 +201,25 @@ static const sig_tone_descriptor_t sig_tones[3] =
     }
 };
 
-static const int tone_present_bits[2] =
+static const int tone_present_bits[3] =
 {
     SIG_TONE_1_PRESENT,
-    SIG_TONE_2_PRESENT
+    SIG_TONE_2_PRESENT,
+    SIG_TONE_1_PRESENT | SIG_TONE_2_PRESENT
 };
 
-static const int tone_change_bits[2] =
+static const int tone_change_bits[3] =
 {
     SIG_TONE_1_CHANGE,
-    SIG_TONE_2_CHANGE
+    SIG_TONE_2_CHANGE,
+    SIG_TONE_1_CHANGE | SIG_TONE_2_CHANGE
+};
+
+static const int coeff_sets[3] =
+{
+    0,
+    1,
+    0
 };
 
 SPAN_DECLARE(int) sig_tone_tx(sig_tone_tx_state_t *s, int16_t amp[], int len)
@@ -273,7 +282,7 @@ SPAN_DECLARE(int) sig_tone_tx(sig_tone_tx_state_t *s, int16_t amp[], int len)
                     for (j = i;  j < i + n;  j++)
                     {
                         tone = dds_mod(&(s->phase_acc[k]), s->phase_rate[k], s->tone_scaling[k][high_low], 0);
-                        amp[j] = saturate(amp[j] + tone);
+                        amp[j] = saturated_add16(amp[j], tone);
                     }
                     /*endfor*/
                 }
@@ -300,6 +309,11 @@ SPAN_DECLARE(void) sig_tone_tx_set_mode(sig_tone_tx_state_t *s, int mode, int du
     if (new_tones  &&  old_tones != new_tones)
         s->high_low_timer = s->desc->high_low_timeout;
     /*endif*/
+    /* If a tone is being turned on, let's start the phase from zero */
+    if ((mode & SIG_TONE_1_PRESENT)  &&  !(s->current_tx_tone & SIG_TONE_1_PRESENT))
+        s->phase_acc[0] = 0;
+    if ((mode & SIG_TONE_2_PRESENT)  &&  !(s->current_tx_tone & SIG_TONE_2_PRESENT))
+        s->phase_acc[1] = 0;
     s->current_tx_tone = mode;
     s->current_tx_timeout = duration;
 }
@@ -352,63 +366,78 @@ SPAN_DECLARE(int) sig_tone_tx_free(sig_tone_tx_state_t *s)
 }
 /*- End of function --------------------------------------------------------*/
 
+int nnn = 0;
+
 SPAN_DECLARE(int) sig_tone_rx(sig_tone_rx_state_t *s, int16_t amp[], int len)
 {
 #if defined(SPANDSP_USE_FIXED_POINT)
     int16_t x;
     int32_t v;
-    int16_t notched_signal[2];
+    int16_t notched_signal[3];
     int16_t bandpass_signal;
+    int16_t signal;
 #else
     float x;
     float v;
-    float notched_signal[2];
+    float notched_signal[3];
     float bandpass_signal;
+    float signal;
 #endif
     int i;
     int j;
-    int32_t notch_power[2];
+    int k;
+    int l;
+    int m;
+    int32_t notch_power[3];
     int32_t flat_power;
+    int immediate;
 
+    l = s->desc->tones;
+    if (l == 2)
+        l = 3;
+    notch_power[1] =
+    notch_power[2] = INT32_MAX;
     for (i = 0;  i < len;  i++)
     {
         if (s->signalling_state_duration < INT_MAX)
             s->signalling_state_duration++;
         /*endif*/
-        for (j = 0;  j < s->desc->tones;  j++)
+        signal = amp[i];
+        for (j = 0;  j < l;  j++)
         {
+            k = coeff_sets[j];
             /* The notch filter is two cascaded biquads. */
 #if defined(SPANDSP_USE_FIXED_POINT)
-            v = ((int32_t) amp[i]*s->desc->notch[j]->a1[0])
-              + ((int32_t) s->tone[j].notch_z1[0]*s->desc->notch[j]->b1[1])
-              + ((int32_t) s->tone[j].notch_z1[1]*s->desc->notch[j]->b1[2]);
+            v = ((int32_t) signal*s->desc->notch[k]->a1[0])
+              + ((int32_t) s->tone[j].notch_z1[0]*s->desc->notch[k]->b1[1])
+              + ((int32_t) s->tone[j].notch_z1[1]*s->desc->notch[k]->b1[2]);
             x = v >> 15;
-            v +=   ((int32_t) s->tone[j].notch_z1[0]*s->desc->notch[j]->a1[1])
-                 + ((int32_t) s->tone[j].notch_z1[1]*s->desc->notch[j]->a1[2]);
+            v +=   ((int32_t) s->tone[j].notch_z1[0]*s->desc->notch[k]->a1[1])
+                 + ((int32_t) s->tone[j].notch_z1[1]*s->desc->notch[k]->a1[2]);
             s->tone[j].notch_z1[1] = s->tone[j].notch_z1[0];
             s->tone[j].notch_z1[0] = x;
-            v +=   ((int32_t) s->tone[j].notch_z2[0]*s->desc->notch[j]->b2[1])
-                 + ((int32_t) s->tone[j].notch_z2[1]*s->desc->notch[j]->b2[2]);
+            v +=   ((int32_t) s->tone[j].notch_z2[0]*s->desc->notch[k]->b2[1])
+                 + ((int32_t) s->tone[j].notch_z2[1]*s->desc->notch[k]->b2[2]);
             x = v >> 15;
-            v +=   ((int32_t) s->tone[j].notch_z2[0]*s->desc->notch[j]->a2[1])
-                 + ((int32_t) s->tone[j].notch_z2[1]*s->desc->notch[j]->a2[2]);
+            v +=   ((int32_t) s->tone[j].notch_z2[0]*s->desc->notch[k]->a2[1])
+                 + ((int32_t) s->tone[j].notch_z2[1]*s->desc->notch[k]->a2[2]);
             s->tone[j].notch_z2[1] = s->tone[j].notch_z2[0];
             s->tone[j].notch_z2[0] = x;
-            notched_signal[j] = v >> s->desc->notch[j]->postscale;
+            notched_signal[j] = v >> s->desc->notch[k]->postscale;
 #else
-            v = amp[i]*s->desc->notch[j]->a1[0]
-              + s->tone[j].notch_z1[0]*s->desc->notch[j]->b1[1]
-              + s->tone[j].notch_z1[1]*s->desc->notch[j]->b1[2];
+            v = signal*s->desc->notch[k]->a1[0]
+              + s->tone[j].notch_z1[0]*s->desc->notch[k]->b1[1]
+              + s->tone[j].notch_z1[1]*s->desc->notch[k]->b1[2];
             x = v;
-            v +=   s->tone[j].notch_z1[0]*s->desc->notch[j]->a1[1]
-                 + s->tone[j].notch_z1[1]*s->desc->notch[j]->a1[2];
+            v +=   s->tone[j].notch_z1[0]*s->desc->notch[k]->a1[1]
+                 + s->tone[j].notch_z1[1]*s->desc->notch[k]->a1[2];
             s->tone[j].notch_z1[1] = s->tone[j].notch_z1[0];
             s->tone[j].notch_z1[0] = x;
-            v +=   s->tone[j].notch_z2[0]*s->desc->notch[j]->b2[1]
-                 + s->tone[j].notch_z2[1]*s->desc->notch[j]->b2[2];
+            v +=   s->tone[j].notch_z2[0]*s->desc->notch[k]->b2[1]
+                 + s->tone[j].notch_z2[1]*s->desc->notch[k]->b2[2];
             x = v;
-            v +=   s->tone[j].notch_z2[0]*s->desc->notch[j]->a2[1]
-                 + s->tone[j].notch_z2[1]*s->desc->notch[j]->a2[2];
+            v +=   s->tone[j].notch_z2[0]*s->desc->notch[k]->a2[1]
+                 + s->tone[j].notch_z2[1]*s->desc->notch[k]->a2[2];
             s->tone[j].notch_z2[1] = s->tone[j].notch_z2[0];
             s->tone[j].notch_z2[0] = x;
             notched_signal[j] = v;
@@ -417,9 +446,10 @@ SPAN_DECLARE(int) sig_tone_rx(sig_tone_rx_state_t *s, int16_t amp[], int len)
                this isn't used in low tone detect mode, but we must keep the
                power measurement rolling along. */
             notch_power[j] = power_meter_update(&s->tone[j].power, notched_signal[j]);
+            if (j == 1)
+                signal = notched_signal[j];
         }
-
-        if (s->tone[0].tone_present  ||  s->tone[1].tone_present)
+        if ((s->signalling_state & (SIG_TONE_1_PRESENT | SIG_TONE_2_PRESENT)))
         {
             if (s->flat_mode_timeout  &&  --s->flat_mode_timeout == 0)
                 s->flat_mode = TRUE;
@@ -432,8 +462,10 @@ SPAN_DECLARE(int) sig_tone_rx(sig_tone_rx_state_t *s, int16_t amp[], int len)
         }
         /*endif*/
 
+        immediate = -1;
         if (s->flat_mode)
         {
+            //printf("Flat mode %d %d\n", s->flat_mode_timeout, s->desc->sharp_flat_timeout);
             /* Flat mode */
             bandpass_signal = amp[i];
             if (s->desc->flat)
@@ -464,10 +496,9 @@ SPAN_DECLARE(int) sig_tone_rx(sig_tone_rx_state_t *s, int16_t amp[], int len)
             flat_power = power_meter_update(&s->flat_power, bandpass_signal);
     
             /* For the flat receiver we use a simple power threshold! */
-            if (s->tone[0].tone_present)
+            if ((s->signalling_state & (SIG_TONE_1_PRESENT | SIG_TONE_2_PRESENT)))
             {
-                s->tone[0].tone_present = (flat_power > s->flat_detection_threshold);
-                if (!s->tone[0].tone_present)
+                if (flat_power < s->flat_detection_threshold)
                 {
                     s->signalling_state &= ~tone_present_bits[0];
                     s->signalling_state |= tone_change_bits[0];
@@ -476,18 +507,15 @@ SPAN_DECLARE(int) sig_tone_rx(sig_tone_rx_state_t *s, int16_t amp[], int len)
             }
             else
             {
-                s->tone[0].tone_present = (flat_power > s->flat_detection_threshold);
-                if (s->tone[0].tone_present)
-                {
+                if (flat_power > s->flat_detection_threshold)
                     s->signalling_state |= (tone_present_bits[0] | tone_change_bits[0]);
-                }
                 /*endif*/
             }
             /*endif*/
 
-            /* Notch insertion logic */    
+            /* Notch insertion logic */
             /* tone_present and tone_on are equivalent in flat mode */
-            if (s->tone[0].tone_present)
+            if ((s->signalling_state & (SIG_TONE_1_PRESENT | SIG_TONE_2_PRESENT)))
             {
                 s->notch_insertion_timeout = s->desc->notch_lag_time;
             }
@@ -504,64 +532,68 @@ SPAN_DECLARE(int) sig_tone_rx(sig_tone_rx_state_t *s, int16_t amp[], int len)
             /* Sharp mode */
             flat_power = power_meter_update(&s->flat_power, amp[i]);
 
-            for (j = 0;  j < s->desc->tones;  j++)
+            /* Persistence checking and notch insertion logic */
+            if (flat_power >= s->sharp_detection_threshold)
+            {
+                /* Which is the better of the single tone responses? */
+                m = (notch_power[0] < notch_power[1])  ?  0  :  1;
+                /* Single tone has precedence. If the better one fails to detect, try
+                   for a dual tone signal. */
+                if ((notch_power[m] >> 6)*s->detection_ratio < (flat_power >> 6))
+                    immediate = m;
+                else if ((notch_power[2] >> 6)*s->detection_ratio < (flat_power >> 7))
+                    immediate = 2;
+            }
+            //printf("Immediate = %d  %d   %d\n", immediate, s->signalling_state, s->tone_persistence_timeout);
+            if ((s->signalling_state & (SIG_TONE_1_PRESENT | SIG_TONE_2_PRESENT)))
             {
-                /* Persistence checking and notch insertion logic */
-                if (s->tone[j].tone_present)
+                if (immediate != s->current_notch_filter)
                 {
-                    if (flat_power < s->sharp_detection_threshold
-                        ||
-                        (notch_power[j] >> 6)*s->detection_ratio > (flat_power >> 6))
-                    {
-                        /* Tone is not detected this sample */
-                        if (--s->tone[j].tone_persistence_timeout == 0)
-                        {
-                            /* Tone off is confirmed */
-                            s->tone[j].tone_present = FALSE;
-                            s->tone[j].tone_persistence_timeout = s->desc->tone_on_check_time;
-                            s->signalling_state &= ~tone_present_bits[j];
-                            s->signalling_state |= tone_change_bits[j];
-                        }
-                        /*endif*/
-                    }
-                    else
+                    /* No tone is detected this sample */
+                    if (--s->tone_persistence_timeout == 0)
                     {
-                        s->tone[j].tone_persistence_timeout = s->desc->tone_off_check_time;
+                        /* Tone off is confirmed */
+                        s->tone_persistence_timeout = s->desc->tone_on_check_time;
+                        s->signalling_state |= ((s->signalling_state & (SIG_TONE_1_PRESENT | SIG_TONE_2_PRESENT)) << 1);
+                        s->signalling_state &= ~(SIG_TONE_1_PRESENT | SIG_TONE_2_PRESENT);
                     }
                     /*endif*/
                 }
                 else
                 {
-                    if (s->notch_insertion_timeout)
-                        s->notch_insertion_timeout--;
-                    /*endif*/
-                    if (flat_power > s->sharp_detection_threshold
-                        &&
-                        (notch_power[j] >> 6)*s->detection_ratio < (flat_power >> 6))
-                    {
-                        /* Tone is detected this sample */
-                        if (--s->tone[j].tone_persistence_timeout == 0)
-                        {
-                            /* Tone on is confirmed */
-                            s->tone[j].tone_present = TRUE;
-                            s->tone[j].tone_persistence_timeout = s->desc->tone_off_check_time;
-                            s->notch_insertion_timeout = s->desc->notch_lag_time;
-                            s->signalling_state |= (tone_present_bits[j] | tone_change_bits[j]);
-                        }
-                        /*endif*/
-                    }
-                    else
+                    s->tone_persistence_timeout = s->desc->tone_off_check_time;
+                }
+                /*endif*/
+            }
+            else
+            {
+                if (s->notch_insertion_timeout)
+                    s->notch_insertion_timeout--;
+                /*endif*/
+                if (immediate >= 0  &&  immediate == s->last_sample_tone_present)
+                {
+                    /* Consistent tone detected this sample */
+                    if (--s->tone_persistence_timeout == 0)
                     {
-                        s->tone[j].tone_persistence_timeout = s->desc->tone_on_check_time;
+                        /* Tone on is confirmed */
+                        s->tone_persistence_timeout = s->desc->tone_off_check_time;
+                        s->notch_insertion_timeout = s->desc->notch_lag_time;
+                        s->signalling_state |= (tone_present_bits[immediate] | tone_change_bits[immediate]);
+                        s->current_notch_filter = immediate;
                     }
                     /*endif*/
                 }
+                else
+                {
+                    s->tone_persistence_timeout = s->desc->tone_on_check_time;
+                }
                 /*endif*/
             }
-            /*endfor*/
+            /*endif*/
+            //printf("XXX %d %d %d %d %d %d\n", nnn++, notch_power[0], notch_power[1], notch_power[2], flat_power, immediate*10000000);
         }
         /*endif*/
-        if (s->signalling_state & (SIG_TONE_1_CHANGE | SIG_TONE_2_CHANGE))
+        if ((s->signalling_state & (SIG_TONE_1_CHANGE | SIG_TONE_2_CHANGE)))
         {
             if (s->sig_update)
                 s->sig_update(s->user_data, s->signalling_state, 0, s->signalling_state_duration);
@@ -574,7 +606,11 @@ SPAN_DECLARE(int) sig_tone_rx(sig_tone_rx_state_t *s, int16_t amp[], int len)
         if ((s->current_rx_tone & SIG_TONE_RX_PASSTHROUGH))
         {
             if ((s->current_rx_tone & SIG_TONE_RX_FILTER_TONE)  ||  s->notch_insertion_timeout)
-                amp[i] = saturate(notched_signal[0]);
+#if defined(SPANDSP_USE_FIXED_POINT)
+                amp[i] = saturate16(notched_signal[s->current_notch_filter]);
+#else
+                amp[i] = fsaturatef(notched_signal[s->current_notch_filter]);
+#endif
             /*endif*/
         }
         else
@@ -583,6 +619,7 @@ SPAN_DECLARE(int) sig_tone_rx(sig_tone_rx_state_t *s, int16_t amp[], int len)
             amp[i] = 0;
         }
         /*endif*/
+        s->last_sample_tone_present = immediate;
     }
     /*endfor*/
     return len;
@@ -620,17 +657,19 @@ SPAN_DECLARE(sig_tone_rx_state_t *) sig_tone_rx_init(sig_tone_rx_state_t *s, int
             s->tone[j].notch_z1[i] = 0.0f;
             s->tone[j].notch_z2[i] = 0.0f;
         }
-        s->flat_z[i] = 0.0f;
     }
+    for (i = 0;  i < 2;  i++)
+        s->flat_z[i] = 0.0f;
 #endif
+    s->last_sample_tone_present = -1;
 
     s->sig_update = sig_update;
     s->user_data = user_data;
 
     s->desc = &sig_tones[tone_type - 1];
 
-    power_meter_init(&s->tone[0].power, 5);
-    power_meter_init(&s->tone[1].power, 5);
+    for (i = 0;  i < 3;  i++)
+        power_meter_init(&s->tone[i].power, 5);
     power_meter_init(&s->flat_power, 5);
 
     s->flat_detection_threshold = power_meter_level_dbm0(s->desc->flat_detection_threshold);
index bb109f830909796d44d9ca9d0249167c4a9ff7a5..514be52334b5d8ad8e65d012664419bc6e7edd22 100644 (file)
@@ -169,6 +169,8 @@ struct sig_tone_rx_state_s
     int current_rx_tone;
     /*! \brief The timeout for switching from the high level to low level tone detector. */
     int high_low_timer;
+    /*! \brief ??? */
+    int current_notch_filter;
 
     struct
     {
@@ -186,11 +188,7 @@ struct sig_tone_rx_state_s
 
         /*! \brief The power output of the notch. */
         power_meter_t power;
-        /*! \brief Persistence check for tone present */
-        int tone_persistence_timeout;
-        /*! \brief TRUE if the tone is declared to be present */
-        int tone_present;
-    } tone[2];
+    } tone[3];
 
 #if defined(SPANDSP_USE_FIXED_POINT)
     /*! \brief The z's for the weighting/bandpass filter. */
@@ -202,6 +200,11 @@ struct sig_tone_rx_state_s
     /*! \brief The output power of the flat (unfiltered or flat filtered) path. */
     power_meter_t flat_power;
 
+    /*! \brief Persistence check for tone present */
+    int tone_persistence_timeout;
+    /*! \brief The tone pattern on the last audio sample */
+    int last_sample_tone_present;
+
     /*! \brief The minimum reading from the power meter for detection in flat mode */
     int32_t flat_detection_threshold;
     /*! \brief The minimum reading from the power meter for detection in sharp mode */
index 77433991ef4b9e2f2d07c1e65c0fc4488cb021bb..78da9fb4c694461112ce0895541c0bd759c045e1 100644 (file)
@@ -70,6 +70,13 @@ const char *bellcore_files[] =
     ""
 };
 
+typedef struct
+{
+    double freq;
+    double min_level;
+    double max_level;
+} template_t;
+
 static int number_of_tones = 1;
 
 static int sampleno = 0;
@@ -81,47 +88,132 @@ static int dial_pulses = 0;
 static int rx_handler_callbacks = 0;
 static int tx_handler_callbacks = 0;
 
+static int use_gui = FALSE;
+
+static void plot_frequency_response(void)
+{
+    FILE *gnucmd;
+    
+    if ((gnucmd = popen("gnuplot", "w")) == NULL)
+    {
+        exit(2);
+    }
+    
+    fprintf(gnucmd, "set autoscale\n");
+    fprintf(gnucmd, "unset log\n");
+    fprintf(gnucmd, "unset label\n");
+    fprintf(gnucmd, "set xtic auto\n");
+    fprintf(gnucmd, "set ytic auto\n");
+    fprintf(gnucmd, "set title 'Notch filter frequency response'\n");
+    fprintf(gnucmd, "set xlabel 'Frequency (Hz)'\n");
+    fprintf(gnucmd, "set ylabel 'Gain (dB)'\n");
+    fprintf(gnucmd, "plot 'sig_tone_notch' using 1:3 title 'min' with lines,"
+                    "'sig_tone_notch' using 1:6 title 'actual' with lines,"
+                    "'sig_tone_notch' using 1:9 title 'max' with lines\n");
+    fflush(gnucmd);
+    getchar();
+    if (pclose(gnucmd) == -1)
+    {
+        exit(2);
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
 static void tx_handler(void *user_data, int what, int level, int duration)
 {
     sig_tone_tx_state_t *s;
+    int tone;
+    int time;
+    static const int pattern_1_tone[][2] =
+    {
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {600, SIG_TONE_1_PRESENT},
+        {0, 0}
+    };
+    static const int pattern_2_tones[][2] =
+    {
+#if 0
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+        {33, SIG_TONE_1_PRESENT},
+        {67, 0},
+#endif
+        {100, SIG_TONE_1_PRESENT},
+        {100, SIG_TONE_1_PRESENT | SIG_TONE_2_PRESENT},
+        {100, SIG_TONE_2_PRESENT},
+#if 0
+        {100, 0},
+        {100, SIG_TONE_2_PRESENT},
+        {100, SIG_TONE_1_PRESENT | SIG_TONE_2_PRESENT},
+        {100, SIG_TONE_1_PRESENT},
+#endif
+        {0, 0}
+    };
     
     s = (sig_tone_tx_state_t *) user_data;
     tx_handler_callbacks++;
     //printf("What - %d, duration - %d\n", what, duration);
     if ((what & SIG_TONE_TX_UPDATE_REQUEST))
     {
-        printf("Tx: update request\n");
         /* The sig tone transmit side wants to know what to do next */
-        switch (tx_section)
+        printf("Tx: update request\n");
+
+        if (number_of_tones == 1)
         {
-        case 0:
-            printf("33ms break - %d samples\n", ms_to_samples(33));
-            tx_section++;
-            sig_tone_tx_set_mode(s, SIG_TONE_1_PRESENT, ms_to_samples(33));
-            break;
-        case 1:
-            printf("67ms make - %d samples\n", ms_to_samples(67));
-            if (++dial_pulses == 9)
-                tx_section++;
-            else
-                tx_section--;
-            /*endif*/
-            sig_tone_tx_set_mode(s, 0, ms_to_samples(67));
-            break;
-        case 2:
+            time = pattern_1_tone[tx_section][0];
+            tone = pattern_1_tone[tx_section][1];
+        }
+        else
+        {
+            time = pattern_2_tones[tx_section][0];
+            tone = pattern_2_tones[tx_section][1];
+        }
+        if (time)
+        {
+            printf("Tx: [%04x] %s %s for %d samples (%dms)\n",
+                   tone,
+                   (tone & SIG_TONE_1_PRESENT)  ?  "on "  :  "off",
+                   (tone & SIG_TONE_2_PRESENT)  ?  "on "  :  "off",
+                   ms_to_samples(time),
+                   time);
+            sig_tone_tx_set_mode(s, tone, ms_to_samples(time));
             tx_section++;
-            printf("600ms on - %d samples\n", ms_to_samples(600));
-            if (number_of_tones == 2)
-                sig_tone_tx_set_mode(s, SIG_TONE_2_PRESENT, ms_to_samples(600));
-            else
-                sig_tone_tx_set_mode(s, SIG_TONE_1_PRESENT, ms_to_samples(600));
-            break;
-        case 3:
+        }
+        else
+        {
             printf("End of sequence\n");
-            sig_tone_tx_set_mode(s, SIG_TONE_1_PRESENT | SIG_TONE_TX_PASSTHROUGH, 0);
-            break;
         }
-        /*endswitch*/
     }
     /*endif*/
 }
@@ -130,30 +222,46 @@ static void tx_handler(void *user_data, int what, int level, int duration)
 static void rx_handler(void *user_data, int what, int level, int duration)
 {
     float ms;
+    int x;
 
     rx_handler_callbacks++;
     ms = 1000.0f*(float) duration/(float) SAMPLE_RATE;
-    printf("What - %d, duration - %d\n", what, duration);
+    printf("Rx: [%04x]", what);
+    x = what & SIG_TONE_1_PRESENT;
     if ((what & SIG_TONE_1_CHANGE))
     {
-        tone_1_present = what & SIG_TONE_1_PRESENT;
-        printf("Rx: tone 1 is %s after %d samples (%fms)\n", (tone_1_present)  ?  "on"  : "off", duration, ms);
+        printf(" %s", (x)  ?  "on "  : "off");
+        if (x == tone_1_present)
+            exit(2);
+        tone_1_present = x;
+    }
+    else
+    {
+        printf(" ---");
+        if (x != tone_1_present)
+            exit(2);
     }
     /*endif*/
+    x = what & SIG_TONE_2_PRESENT;
     if ((what & SIG_TONE_2_CHANGE))
     {
-        tone_2_present = what & SIG_TONE_2_PRESENT;
-        printf("Rx: tone 2 is %s after %d samples (%fms)\n", (tone_2_present)  ?  "on"  : "off", duration, ms);
+        printf(" %s", (x)  ?  "on "  : "off");
+        if (x == tone_2_present)
+            exit(2);
+        tone_2_present = x;
+    }
+    else
+    {
+        if (x != tone_2_present)
+            exit(2);
+        printf(" ---");
     }
     /*endif*/
+    printf(" after %d samples (%.3fms)\n", duration, ms);
 }
 /*- End of function --------------------------------------------------------*/
 
-static void map_frequency_response(sig_tone_rx_state_t *s,
-                                   double f1,
-                                   double f2,
-                                   double f3,
-                                   double f4)
+static void map_frequency_response(sig_tone_rx_state_t *s, template_t template[])
 {
     int16_t buf[SAMPLES_PER_CHUNK];
     int i;
@@ -163,12 +271,16 @@ static void map_frequency_response(sig_tone_rx_state_t *s,
     swept_tone_state_t *swept;
     double freq;
     double gain;
+    int template_entry;
+    FILE *file;
     
     /* Things like noise don't highlight the frequency response of the high Q notch
        very well. We use a slowly swept frequency to check it. */
     printf("Frequency response test\n");
     sig_tone_rx_set_mode(s, SIG_TONE_RX_PASSTHROUGH | SIG_TONE_RX_FILTER_TONE, 0);
     swept = swept_tone_init(NULL, 200.0f, 3900.0f, -10.0f, 120*SAMPLE_RATE, 0);
+    template_entry = 0;
+    file = fopen("sig_tone_notch", "wb");
     for (;;)
     {
         if ((len = swept_tone(swept, buf, SAMPLES_PER_CHUNK)) <= 0)
@@ -188,22 +300,43 @@ static void map_frequency_response(sig_tone_rx_state_t *s,
             gain = 10.0*log10(sumout/sumin);
         else
             gain = 0.0;
-        printf("%7.1f Hz %f dBm0\n", freq, gain);
-        if (gain > 0.0
-            ||
-            (freq < f1  &&  gain < -1.0)
-            ||
-            (freq > f2  &&  freq < f3  &&  gain > -30.0)
-            ||
-            (freq > f4  &&  gain < -1.0))
+        printf("%7.1f Hz %.3f dBm0 < %.3f dBm0 < %.3f dBm0\n",
+               freq,
+               template[template_entry].min_level,
+               gain,
+               template[template_entry].max_level);
+        if (file)
         {
+            fprintf(file,
+                    "%7.1f Hz %.3f dBm0 < %.3f dBm0 < %.3f dBm0\n",
+                    freq,
+                    template[template_entry].min_level,
+                    gain,
+                    template[template_entry].max_level);
+        }
+        /*endif*/
+        if (gain < template[template_entry].min_level  ||  gain > template[template_entry].max_level)
+        {
+            printf("Expected: %.3f dBm0 to  %.3f dBm0\n",
+                   template[template_entry].min_level,
+                   template[template_entry].max_level);
             printf("    Failed\n");
             exit(2);
         }
         /*endif*/
+        if (freq > template[template_entry].freq)
+            template_entry++;
     }
     /*endfor*/
     swept_tone_free(swept);
+    if (file)
+    {
+        fclose(file);
+        if (use_gui)
+            plot_frequency_response();
+        /*endif*/
+    }
+    /*endif*/
     printf("    Passed\n");
 }
 /*- End of function --------------------------------------------------------*/
@@ -256,16 +389,17 @@ static void speech_immunity_tests(sig_tone_rx_state_t *s)
 }
 /*- End of function --------------------------------------------------------*/
 
-static void level_and_ratio_tests(sig_tone_rx_state_t *s, double pitch)
+static void level_and_ratio_tests(sig_tone_rx_state_t *s, double pitch[2])
 {
     awgn_state_t noise_source;
-    int32_t phase_rate;
-    uint32_t phase;
+    int32_t phase_rate[2];
+    uint32_t phase[2];
     int16_t gain;
     int16_t amp[SAMPLE_RATE];
     int i;
     int j;
     int k;
+    int l;
     float noise_level;
     float tone_level;
     power_meter_t noise_meter;
@@ -273,9 +407,12 @@ static void level_and_ratio_tests(sig_tone_rx_state_t *s, double pitch)
     int16_t noise;
     int16_t tone;
 
-    printf("Acceptable level and ratio test\n");
-    phase = 0;
-    phase_rate = dds_phase_rate(pitch);
+    printf("Acceptable level and ratio test - %.2f Hz + %.2f Hz\n", pitch[0], pitch[1]);
+    for (l = 0;  l < 2;  l++)
+    {
+        phase[l] = 0;
+        phase_rate[l] = (pitch[l] != 0.0)  ?  dds_phase_rate(pitch[l])  :  0;
+    }
     for (k = -25;  k > -60;  k--)
     {
         noise_level = k;
@@ -293,7 +430,9 @@ static void level_and_ratio_tests(sig_tone_rx_state_t *s, double pitch)
             for (i = 0;  i < SAMPLES_PER_CHUNK;  i++)
             {
                 noise = awgn(&noise_source);
-                tone = dds_mod(&phase, phase_rate, gain, 0);
+                tone = dds_mod(&phase[0], phase_rate[0], gain, 0);
+                if (phase_rate[1])
+                    tone += dds_mod(&phase[1], phase_rate[1], gain, 0);
                 power_meter_update(&noise_meter, noise);
                 power_meter_update(&tone_meter, tone);
                 amp[i] = noise + tone;
@@ -302,8 +441,10 @@ static void level_and_ratio_tests(sig_tone_rx_state_t *s, double pitch)
             sig_tone_rx(s, amp, SAMPLES_PER_CHUNK);
             if (rx_handler_callbacks)
             {
-                printf("Hit at tone = %fdBm0, noise = %fdBm0\n", tone_level, noise_level);
-                printf("Noise = %fdBm0, tone = %fdBm0\n", power_meter_current_dbm0(&noise_meter), power_meter_current_dbm0(&tone_meter));
+                printf("Hit at   tone = %.2fdBm0, noise = %.2fdBm0\n", tone_level, noise_level);
+                printf("Measured tone = %.2fdBm0, noise = %.2fdBm0\n", power_meter_current_dbm0(&tone_meter), power_meter_current_dbm0(&noise_meter));
+                if (rx_handler_callbacks != 1)
+                    printf("Callbacks = %d\n", rx_handler_callbacks);
             }
             /*endif*/
             tone_level += 1.0f;
@@ -327,6 +468,7 @@ static void sequence_tests(sig_tone_tx_state_t *tx_state, sig_tone_rx_state_t *r
     int tx_samples;
 
     printf("Signalling sequence test\n");
+    tx_section = 0;
     if ((outhandle = sf_open_telephony_write(OUT_FILE_NAME, 2)) == NULL)
     {
         fprintf(stderr, "    Cannot create audio file '%s'\n", OUT_FILE_NAME);
@@ -335,12 +477,14 @@ static void sequence_tests(sig_tone_tx_state_t *tx_state, sig_tone_rx_state_t *r
     /*endif*/
 
     awgn_init_dbm0(&noise_source, 1234567, -20.0f);
-    for (sampleno = 0;  sampleno < 60000;  sampleno += SAMPLES_PER_CHUNK)
+    sig_tone_tx_set_mode(tx_state, SIG_TONE_1_PRESENT | SIG_TONE_2_PRESENT | SIG_TONE_TX_PASSTHROUGH, 0);
+    sig_tone_rx_set_mode(rx_state, SIG_TONE_RX_PASSTHROUGH, 0);
+    for (sampleno = 0;  sampleno < 4000;  sampleno += SAMPLES_PER_CHUNK)
     {
-        if (sampleno == 8000)
+        if (sampleno == 800)
         {
             /* 100ms seize */
-            printf("100ms seize - %d samples\n", ms_to_samples(100));
+            printf("Tx: [0000] off off for %d samples (%dms)\n", ms_to_samples(100), 100);
             dial_pulses = 0;
             sig_tone_tx_set_mode(tx_state, 0, ms_to_samples(100));
         }
@@ -381,24 +525,40 @@ int main(int argc, char *argv[])
     sig_tone_tx_state_t tx_state;
     sig_tone_rx_state_t rx_state;
     codec_munge_state_t *munge;
-    double f1;
-    double f2;
-    double fc;
-    double f3;
-    double f4;
+    double fc[2];
+    int i;
+    template_t template[10];
+    int opt;
+
+    use_gui = FALSE;
+    while ((opt = getopt(argc, argv, "g")) != -1)
+    {
+        switch (opt)
+        {
+        case 'g':
+            use_gui = TRUE;
+            break;
+        default:
+            //usage();
+            exit(2);
+            break;
+        }
+    }
 
     for (type = 1;  type <= 3;  type++)
     {
         sampleno = 0;
         tone_1_present = 0;
         tone_2_present = 0;
-        tx_section = 0;
         munge = NULL;
-        f1 =
-        f2 =
-        fc =
-        f3 =
-        f4 = 0.0;
+        for (i = 0;  i < 10;  i++)
+        {
+            template[i].freq = 0.0;
+            template[i].min_level = 0.0;
+            template[i].max_level = 0.0;
+        }
+        fc[0] =
+        fc[1] = 0.0;
         switch (type)
         {
         case 1:
@@ -407,11 +567,33 @@ int main(int argc, char *argv[])
             sig_tone_tx_init(&tx_state, SIG_TONE_2280HZ, tx_handler, &tx_state);
             sig_tone_rx_init(&rx_state, SIG_TONE_2280HZ, rx_handler, &rx_state);
             number_of_tones = 1;
-            f1 = 2280.0 - 200.0;
-            f2 = 2280.0 - 20.0;
-            fc = 2280.0;
-            f3 = 2280.0 + 20.0;
-            f4 = 2280.0 + 200.0;
+            fc[0] = 2280.0;
+
+            /* From BTNR 181 2.3.3.1 */
+            template[0].freq = 1150.0;
+            template[0].min_level = -0.2;
+            template[0].max_level = 0.0;
+            template[1].freq = 1880.0;
+            template[1].min_level = -0.5;
+            template[1].max_level = 0.0;
+            template[2].freq = 2080.0;
+            template[2].min_level = -5.0;
+            template[2].max_level = 0.0;
+            template[3].freq = 2280.0 - 20.0;
+            template[3].min_level = -99.0;
+            template[3].max_level = 0.0;
+            template[4].freq = 2280.0 + 20.0;
+            template[4].min_level = -99.0;
+            template[4].max_level = -30.0;
+            template[5].freq = 2480.0;
+            template[5].min_level = -99.0;
+            template[5].max_level = 0.0;
+            template[6].freq = 2680.0;
+            template[6].min_level = -5.0;
+            template[6].max_level = 0.0;
+            template[7].freq = 4000.0;
+            template[7].min_level = -0.5;
+            template[7].max_level = 0.0;
             break;
         case 2:
             printf("2600Hz tests.\n");
@@ -419,11 +601,23 @@ int main(int argc, char *argv[])
             sig_tone_tx_init(&tx_state, SIG_TONE_2600HZ, tx_handler, &tx_state);
             sig_tone_rx_init(&rx_state, SIG_TONE_2600HZ, rx_handler, &rx_state);
             number_of_tones = 1;
-            f1 = 2600.0 - 200.0;
-            f2 = 2600.0 - 20.0;
-            fc = 2600.0;
-            f3 = 2600.0 + 20.0;
-            f4 = 2600.0 + 200.0;
+            fc[0] = 2600.0;
+
+            template[0].freq = 2600.0 - 200.0;
+            template[0].min_level = -1.0;
+            template[0].max_level = 0.0;
+            template[1].freq = 2600.0 - 20.0;
+            template[1].min_level = -99.0;
+            template[1].max_level = 0.0;
+            template[2].freq = 2600.0 + 20.0;
+            template[2].min_level = -99.0;
+            template[2].max_level = -30.0;
+            template[3].freq = 2600.0 + 200.0;
+            template[3].min_level = -99.0;
+            template[3].max_level = 0.0;
+            template[4].freq = 4000.0;
+            template[4].min_level = -1.0;
+            template[4].max_level = 0.0;
             break;
         case 3:
             printf("2400Hz/2600Hz tests.\n");
@@ -431,21 +625,36 @@ int main(int argc, char *argv[])
             sig_tone_tx_init(&tx_state, SIG_TONE_2400HZ_2600HZ, tx_handler, &tx_state);
             sig_tone_rx_init(&rx_state, SIG_TONE_2400HZ_2600HZ, rx_handler, &rx_state);
             number_of_tones = 2;
-            f1 = 2400.0 - 200.0;
-            f2 = 2400.0 - 20.0;
-            fc = 2400.0;
-            f3 = 2400.0 + 20.0;
-            f4 = 2400.0 + 200.0;
+            fc[0] = 2400.0;
+            fc[1] = 2600.0;
+
+            template[0].freq = 2400.0 - 200.0;
+            template[0].min_level = -1.0;
+            template[0].max_level = 0.0;
+            template[1].freq = 2400.0 - 20.0;
+            template[1].min_level = -99.0;
+            template[1].max_level = 0.0;
+            template[2].freq = 2400.0 + 20.0;
+            template[2].min_level = -99.0;
+            template[2].max_level = -30.0;
+            template[3].freq = 2600.0 - 20.0;
+            template[3].min_level = -99.0;
+            template[3].max_level = 0.0;
+            template[4].freq = 2600.0 + 20.0;
+            template[4].min_level = -99.0;
+            template[4].max_level = -30.0;
+            template[5].freq = 2600.0 + 200.0;
+            template[5].min_level = -99.0;
+            template[5].max_level = 0.0;
+            template[6].freq = 4000.0;
+            template[6].min_level = -1.0;
+            template[6].max_level = 0.0;
             break;
         }
         /*endswitch*/
-        /* Set to the default on hook condition */
-        map_frequency_response(&rx_state, f1, f2, f3, f4);
+        map_frequency_response(&rx_state, template);
         speech_immunity_tests(&rx_state);
         level_and_ratio_tests(&rx_state, fc);
-
-        sig_tone_tx_set_mode(&tx_state, SIG_TONE_1_PRESENT | SIG_TONE_2_PRESENT | SIG_TONE_TX_PASSTHROUGH, 0);
-        sig_tone_rx_set_mode(&rx_state, SIG_TONE_RX_PASSTHROUGH, 0);
         sequence_tests(&tx_state, &rx_state, munge);
     }
     /*endfor*/