diff spandsp-0.0.6pre17/src/bell_r2_mf.c @ 4:26cd8f1ef0b1

import spandsp-0.0.6pre17
author Peter Meerwald <pmeerw@cosy.sbg.ac.at>
date Fri, 25 Jun 2010 15:50:58 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spandsp-0.0.6pre17/src/bell_r2_mf.c	Fri Jun 25 15:50:58 2010 +0200
@@ -0,0 +1,863 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * bell_r2_mf.c - Bell MF and MFC/R2 tone generation and detection.
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2001 Steve Underwood
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1,
+ * as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Id: bell_r2_mf.c,v 1.39.4.1 2009/12/23 14:23:48 steveu Exp $
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#if defined(HAVE_TGMATH_H)
+#include <tgmath.h>
+#endif
+#if defined(HAVE_MATH_H)
+#include <math.h>
+#endif
+#include "floating_fudge.h"
+
+#include "spandsp/telephony.h"
+#include "spandsp/fast_convert.h"
+#include "spandsp/queue.h"
+#include "spandsp/dc_restore.h"
+#include "spandsp/complex.h"
+#include "spandsp/dds.h"
+#include "spandsp/tone_detect.h"
+#include "spandsp/tone_generate.h"
+#include "spandsp/super_tone_rx.h"
+#include "spandsp/dtmf.h"
+#include "spandsp/bell_r2_mf.h"
+
+#include "spandsp/private/queue.h"
+#include "spandsp/private/tone_generate.h"
+#include "spandsp/private/bell_r2_mf.h"
+
+#if !defined(M_PI)
+/* C99 systems may not define M_PI */
+#define M_PI 3.14159265358979323846264338327
+#endif
+
+/*!
+    MF tone descriptor.
+*/
+typedef struct
+{
+    int         f1;         /* First freq */
+    int         f2;         /* Second freq */
+    int8_t      level1;     /* Level of the first freq (dB) */
+    int8_t      level2;     /* Level of the second freq (dB) */
+    uint8_t     on_time;    /* Tone on time (ms) */
+    uint8_t     off_time;   /* Minimum post tone silence (ms) */
+} mf_digit_tones_t;
+
+int bell_mf_gen_inited = FALSE;
+tone_gen_descriptor_t bell_mf_digit_tones[15];
+
+int r2_mf_gen_inited = FALSE;
+tone_gen_descriptor_t r2_mf_fwd_digit_tones[15];
+tone_gen_descriptor_t r2_mf_back_digit_tones[15];
+
+#if 0
+tone_gen_descriptor_t socotel_mf_digit_tones[18];
+#endif
+
+/* Bell R1 tone generation specs.
+ *  Power: -7dBm +- 1dB
+ *  Frequency: within +-1.5%
+ *  Mismatch between the start time of a pair of tones: <=6ms.
+ *  Mismatch between the end time of a pair of tones: <=6ms.
+ *  Tone duration: 68+-7ms, except KP which is 100+-7ms.
+ *  Inter-tone gap: 68+-7ms.
+ */
+static const mf_digit_tones_t bell_mf_tones[] =
+{
+    { 700,  900, -7, -7,  68, 68},
+    { 700, 1100, -7, -7,  68, 68},
+    { 900, 1100, -7, -7,  68, 68},
+    { 700, 1300, -7, -7,  68, 68},
+    { 900, 1300, -7, -7,  68, 68},
+    {1100, 1300, -7, -7,  68, 68},
+    { 700, 1500, -7, -7,  68, 68},
+    { 900, 1500, -7, -7,  68, 68},
+    {1100, 1500, -7, -7,  68, 68},
+    {1300, 1500, -7, -7,  68, 68},
+    { 700, 1700, -7, -7,  68, 68}, /* ST''' - use 'C' */
+    { 900, 1700, -7, -7,  68, 68}, /* ST'   - use 'A' */
+    {1100, 1700, -7, -7, 100, 68}, /* KP    - use '*' */
+    {1300, 1700, -7, -7,  68, 68}, /* ST''  - use 'B' */
+    {1500, 1700, -7, -7,  68, 68}, /* ST    - use '#' */
+    {0, 0, 0, 0, 0, 0}
+};
+
+/* The order of the digits here must match the list above */
+static const char bell_mf_tone_codes[] = "1234567890CA*B#";
+
+/* R2 tone generation specs.
+ *  Power: -11.5dBm +- 1dB
+ *  Frequency: within +-4Hz
+ *  Mismatch between the start time of a pair of tones: <=1ms.
+ *  Mismatch between the end time of a pair of tones: <=1ms.
+ */
+static const mf_digit_tones_t r2_mf_fwd_tones[] =
+{
+    {1380, 1500, -11, -11, 1, 0},
+    {1380, 1620, -11, -11, 1, 0},
+    {1500, 1620, -11, -11, 1, 0},
+    {1380, 1740, -11, -11, 1, 0},
+    {1500, 1740, -11, -11, 1, 0},
+    {1620, 1740, -11, -11, 1, 0},
+    {1380, 1860, -11, -11, 1, 0},
+    {1500, 1860, -11, -11, 1, 0},
+    {1620, 1860, -11, -11, 1, 0},
+    {1740, 1860, -11, -11, 1, 0},
+    {1380, 1980, -11, -11, 1, 0},
+    {1500, 1980, -11, -11, 1, 0},
+    {1620, 1980, -11, -11, 1, 0},
+    {1740, 1980, -11, -11, 1, 0},
+    {1860, 1980, -11, -11, 1, 0},
+    {0, 0, 0, 0, 0, 0}
+};
+
+static const mf_digit_tones_t r2_mf_back_tones[] =
+{
+    {1140, 1020, -11, -11, 1, 0},
+    {1140,  900, -11, -11, 1, 0},
+    {1020,  900, -11, -11, 1, 0},
+    {1140,  780, -11, -11, 1, 0},
+    {1020,  780, -11, -11, 1, 0},
+    { 900,  780, -11, -11, 1, 0},
+    {1140,  660, -11, -11, 1, 0},
+    {1020,  660, -11, -11, 1, 0},
+    { 900,  660, -11, -11, 1, 0},
+    { 780,  660, -11, -11, 1, 0},
+    {1140,  540, -11, -11, 1, 0},
+    {1020,  540, -11, -11, 1, 0},
+    { 900,  540, -11, -11, 1, 0},
+    { 780,  540, -11, -11, 1, 0},
+    { 660,  540, -11, -11, 1, 0},
+    {0, 0, 0, 0, 0, 0}
+};
+
+/* The order of the digits here must match the lists above */
+static const char r2_mf_tone_codes[] = "1234567890BCDEF";
+
+#if 0
+static const mf_digit_tones_t socotel_tones[] =
+{
+    { 700,  900, -11, -11, 1, 0},
+    { 700, 1100, -11, -11, 1, 0},
+    { 900, 1100, -11, -11, 1, 0},
+    { 700, 1300, -11, -11, 1, 0},
+    { 900, 1300, -11, -11, 1, 0},
+    {1100, 1300, -11, -11, 1, 0},
+    { 700, 1500, -11, -11, 1, 0},
+    { 900, 1500, -11, -11, 1, 0},
+    {1100, 1500, -11, -11, 1, 0},
+    {1300, 1500, -11, -11, 1, 0},
+    {1500, 1700, -11, -11, 1, 0},
+    { 700, 1700, -11, -11, 1, 0},
+    { 900, 1700, -11, -11, 1, 0},
+    {1300, 1700, -11, -11, 1, 0},
+    {1100, 1700, -11, -11, 1, 0},
+    {1700,    0, -11, -11, 1, 0},   /* Use 'F' */
+    {1900,    0, -11, -11, 1, 0},   /* Use 'G' */
+    {0, 0, 0, 0, 0, 0}
+};
+
+/* The order of the digits here must match the list above */
+static char socotel_mf_tone_codes[] = "1234567890ABCDEFG";
+#endif
+
+#if defined(SPANDSP_USE_FIXED_POINT)
+#define BELL_MF_THRESHOLD           204089              /* -30.5dBm0 */
+#define BELL_MF_TWIST               3.981f              /* 6dB */
+#define BELL_MF_RELATIVE_PEAK       12.589f             /* 11dB */
+#define BELL_MF_SAMPLES_PER_BLOCK   120
+
+#define R2_MF_THRESHOLD             62974               /* -36.5dBm0 */
+#define R2_MF_TWIST                 5.012f              /* 7dB */
+#define R2_MF_RELATIVE_PEAK         12.589f             /* 11dB */
+#define R2_MF_SAMPLES_PER_BLOCK     133
+#else
+#define BELL_MF_THRESHOLD           3343803100.0f       /* -30.5dBm0 [((120.0*32768.0/1.4142)*10^((-30.5 - DBM0_MAX_SINE_POWER)/20.0))^2 => 3343803100.0] */
+#define BELL_MF_TWIST               3.981f              /* 6dB [10^(6/10) => 3.981] */
+#define BELL_MF_RELATIVE_PEAK       12.589f             /* 11dB */
+#define BELL_MF_SAMPLES_PER_BLOCK   120
+
+#define R2_MF_THRESHOLD             1031766650.0f       /* -36.5dBm0 [((133.0*32768.0/1.4142)*10^((-36.5 - DBM0_MAX_SINE_POWER)/20.0))^2 => 1031766650.0] */
+#define R2_MF_TWIST                 5.012f              /* 7dB */
+#define R2_MF_RELATIVE_PEAK         12.589f             /* 11dB */
+#define R2_MF_SAMPLES_PER_BLOCK     133
+#endif
+
+static goertzel_descriptor_t bell_mf_detect_desc[6];
+
+static goertzel_descriptor_t mf_fwd_detect_desc[6];
+static goertzel_descriptor_t mf_back_detect_desc[6];
+
+static const int bell_mf_frequencies[] =
+{
+     700,  900, 1100, 1300, 1500, 1700
+};
+
+/* Use the follow characters for the Bell MF special signals:
+    KP    - use '*'
+    ST    - use '#'
+    ST'   - use 'A'
+    ST''  - use 'B'
+    ST''' - use 'C' */
+static const char bell_mf_positions[] = "1247C-358A--69*---0B----#";
+
+static const int r2_mf_fwd_frequencies[] =
+{
+    1380, 1500, 1620, 1740, 1860, 1980
+};
+
+static const int r2_mf_back_frequencies[] =
+{
+    1140, 1020,  900,  780,  660,  540
+};
+
+/* Use codes '1' to 'F' for the R2 signals 1 to 15, except for signal 'A'.
+   Use '0' for this, so the codes match the digits 0-9. */
+static const char r2_mf_positions[] = "1247B-358C--69D---0E----F";
+
+static void bell_mf_gen_init(void)
+{
+    int i;
+    const mf_digit_tones_t *tones;
+
+    if (bell_mf_gen_inited)
+        return;
+    i = 0;
+    tones = bell_mf_tones;
+    while (tones->on_time)
+    {
+        /* Note: The duration of KP is longer than the other signals. */
+        make_tone_gen_descriptor(&bell_mf_digit_tones[i++],
+                                 tones->f1,
+                                 tones->level1,
+                                 tones->f2,
+                                 tones->level2,
+                                 tones->on_time,
+                                 tones->off_time,
+                                 0,
+                                 0,
+                                 FALSE);
+        tones++;
+    }
+    bell_mf_gen_inited = TRUE;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) bell_mf_tx(bell_mf_tx_state_t *s, int16_t amp[], int max_samples)
+{
+    int len;
+    const char *cp;
+    int digit;
+
+    len = 0;
+    if (s->tones.current_section >= 0)
+    {
+        /* Deal with the fragment left over from last time */
+        len = tone_gen(&(s->tones), amp, max_samples);
+    }
+    while (len < max_samples  &&  (digit = queue_read_byte(&s->queue.queue)) >= 0)
+    {
+        /* Step to the next digit */
+        if ((cp = strchr(bell_mf_tone_codes, digit)) == NULL)
+            continue;
+        tone_gen_init(&(s->tones), &bell_mf_digit_tones[cp - bell_mf_tone_codes]);
+        len += tone_gen(&(s->tones), amp + len, max_samples - len);
+    }
+    return len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) bell_mf_tx_put(bell_mf_tx_state_t *s, const char *digits, int len)
+{
+    size_t space;
+
+    /* This returns the number of characters that would not fit in the buffer.
+       The buffer will only be loaded if the whole string of digits will fit,
+       in which case zero is returned. */
+    if (len < 0)
+    {
+        if ((len = strlen(digits)) == 0)
+            return 0;
+    }
+    if ((space = queue_free_space(&s->queue.queue)) < (size_t) len)
+        return len - (int) space;
+    if (queue_write(&s->queue.queue, (const uint8_t *) digits, len) >= 0)
+        return 0;
+    return -1;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(bell_mf_tx_state_t *) bell_mf_tx_init(bell_mf_tx_state_t *s)
+{
+    if (s == NULL)
+    {
+        if ((s = (bell_mf_tx_state_t *) malloc(sizeof(*s))) == NULL)
+            return NULL;
+    }
+    memset(s, 0, sizeof(*s));
+
+    if (!bell_mf_gen_inited)
+        bell_mf_gen_init();
+    tone_gen_init(&(s->tones), &bell_mf_digit_tones[0]);
+    s->current_sample = 0;
+    queue_init(&s->queue.queue, MAX_BELL_MF_DIGITS, QUEUE_READ_ATOMIC | QUEUE_WRITE_ATOMIC);
+    s->tones.current_section = -1;
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) bell_mf_tx_release(bell_mf_tx_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) bell_mf_tx_free(bell_mf_tx_state_t *s)
+{
+    free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) r2_mf_tx(r2_mf_tx_state_t *s, int16_t amp[], int samples)
+{
+    int len;
+
+    if (s->digit == 0)
+    {
+        len = samples;
+        memset(amp, 0, len*sizeof(int16_t));
+    }
+    else
+    {
+        len = tone_gen(&s->tone, amp, samples);
+    }
+    return len;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) r2_mf_tx_put(r2_mf_tx_state_t *s, char digit)
+{
+    char *cp;
+
+    if (digit  &&  (cp = strchr(r2_mf_tone_codes, digit)))
+    {
+        if (s->fwd)
+            tone_gen_init(&s->tone, &r2_mf_fwd_digit_tones[cp - r2_mf_tone_codes]);
+        else
+            tone_gen_init(&s->tone, &r2_mf_back_digit_tones[cp - r2_mf_tone_codes]);
+        s->digit = digit;
+    }
+    else
+    {
+        s->digit = 0;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(r2_mf_tx_state_t *) r2_mf_tx_init(r2_mf_tx_state_t *s, int fwd)
+{
+    int i;
+    const mf_digit_tones_t *tones;
+
+    if (s == NULL)
+    {
+        if ((s = (r2_mf_tx_state_t *) malloc(sizeof(*s))) == NULL)
+            return NULL;
+    }
+    memset(s, 0, sizeof(*s));
+
+    if (!r2_mf_gen_inited)
+    {
+        i = 0;
+        tones = r2_mf_fwd_tones;
+        while (tones->on_time)
+        {
+            make_tone_gen_descriptor(&r2_mf_fwd_digit_tones[i++],
+                                     tones->f1,
+                                     tones->level1,
+                                     tones->f2,
+                                     tones->level2,
+                                     tones->on_time,
+                                     tones->off_time,
+                                     0,
+                                     0,
+                                     (tones->off_time == 0));
+            tones++;
+        }
+        i = 0;
+        tones = r2_mf_back_tones;
+        while (tones->on_time)
+        {
+            make_tone_gen_descriptor(&r2_mf_back_digit_tones[i++],
+                                     tones->f1,
+                                     tones->level1,
+                                     tones->f2,
+                                     tones->level2,
+                                     tones->on_time,
+                                     tones->off_time,
+                                     0,
+                                     0,
+                                     (tones->off_time == 0));
+            tones++;
+        }
+        r2_mf_gen_inited = TRUE;
+    }
+    s->fwd = fwd;
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) r2_mf_tx_release(r2_mf_tx_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) r2_mf_tx_free(r2_mf_tx_state_t *s)
+{
+    free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) bell_mf_rx(bell_mf_rx_state_t *s, const int16_t amp[], int samples)
+{
+#if defined(SPANDSP_USE_FIXED_POINT)
+    int32_t energy[6];
+    int16_t xamp;
+#else
+    float energy[6];
+    float xamp;
+#endif
+    int i;
+    int j;
+    int sample;
+    int best;
+    int second_best;
+    int limit;
+    uint8_t hit;
+
+    hit = 0;
+    for (sample = 0;  sample < samples;  sample = limit)
+    {
+        if ((samples - sample) >= (BELL_MF_SAMPLES_PER_BLOCK - s->current_sample))
+            limit = sample + (BELL_MF_SAMPLES_PER_BLOCK - s->current_sample);
+        else
+            limit = samples;
+        for (j = sample;  j < limit;  j++)
+        {
+            xamp = goertzel_preadjust_amp(amp[j]);
+            goertzel_samplex(&s->out[0], xamp);
+            goertzel_samplex(&s->out[1], xamp);
+            goertzel_samplex(&s->out[2], xamp);
+            goertzel_samplex(&s->out[3], xamp);
+            goertzel_samplex(&s->out[4], xamp);
+            goertzel_samplex(&s->out[5], xamp);
+        }
+        s->current_sample += (limit - sample);
+        if (s->current_sample < BELL_MF_SAMPLES_PER_BLOCK)
+            continue;
+
+        /* We are at the end of an MF detection block */
+        /* Find the two highest energies. The spec says to look for
+           two tones and two tones only. Taking this literally -ie
+           only two tones pass the minimum threshold - doesn't work
+           well. The sinc function mess, due to rectangular windowing
+           ensure that! Find the two highest energies and ensure they
+           are considerably stronger than any of the others. */
+        energy[0] = goertzel_result(&s->out[0]);
+        energy[1] = goertzel_result(&s->out[1]);
+        if (energy[0] > energy[1])
+        {
+            best = 0;
+            second_best = 1;
+        }
+        else
+        {
+            best = 1;
+            second_best = 0;
+        }
+        for (i = 2;  i < 6;  i++)
+        {
+            energy[i] = goertzel_result(&s->out[i]);
+            if (energy[i] >= energy[best])
+            {
+                second_best = best;
+                best = i;
+            }
+            else if (energy[i] >= energy[second_best])
+            {
+                second_best = i;
+            }
+        }
+        /* Basic signal level and twist tests */
+        hit = 0;
+        if (energy[best] >= BELL_MF_THRESHOLD
+            &&
+            energy[second_best] >= BELL_MF_THRESHOLD
+            &&
+            energy[best] < energy[second_best]*BELL_MF_TWIST
+            &&
+            energy[best]*BELL_MF_TWIST > energy[second_best])
+        {
+            /* Relative peak test */
+            hit = 'X';
+            for (i = 0;  i < 6;  i++)
+            {
+                if (i != best  &&  i != second_best)
+                {
+                    if (energy[i]*BELL_MF_RELATIVE_PEAK >= energy[second_best])
+                    {
+                        /* The best two are not clearly the best */
+                        hit = 0;
+                        break;
+                    }
+                }
+            }
+        }
+        if (hit)
+        {
+            /* Get the values into ascending order */
+            if (second_best < best)
+            {
+                i = best;
+                best = second_best;
+                second_best = i;
+            }
+            best = best*5 + second_best - 1;
+            hit = bell_mf_positions[best];
+            /* Look for two successive similar results */
+            /* The logic in the next test is:
+               For KP we need 4 successive identical clean detects, with
+               two blocks of something different preceeding it. For anything
+               else we need two successive identical clean detects, with
+               two blocks of something different preceeding it. */
+            if (hit == s->hits[4]
+                &&
+                hit == s->hits[3]
+                &&
+                   ((hit != '*'  &&  hit != s->hits[2]  &&  hit != s->hits[1])
+                    ||
+                    (hit == '*'  &&  hit == s->hits[2]  &&  hit != s->hits[1]  &&  hit != s->hits[0])))
+            {
+                if (s->current_digits < MAX_BELL_MF_DIGITS)
+                {
+                    s->digits[s->current_digits++] = (char) hit;
+                    s->digits[s->current_digits] = '\0';
+                    if (s->digits_callback)
+                    {
+                        s->digits_callback(s->digits_callback_data, s->digits, s->current_digits);
+                        s->current_digits = 0;
+                    }
+                }
+                else
+                {
+                    s->lost_digits++;
+                }
+            }
+        }
+        s->hits[0] = s->hits[1];
+        s->hits[1] = s->hits[2];
+        s->hits[2] = s->hits[3];
+        s->hits[3] = s->hits[4];
+        s->hits[4] = hit;
+        s->current_sample = 0;
+    }
+    if (s->current_digits  &&  s->digits_callback)
+    {
+        s->digits_callback(s->digits_callback_data, s->digits, s->current_digits);
+        s->digits[0] = '\0';
+        s->current_digits = 0;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(size_t) bell_mf_rx_get(bell_mf_rx_state_t *s, char *buf, int max)
+{
+    if (max > s->current_digits)
+        max = s->current_digits;
+    if (max > 0)
+    {
+        memcpy(buf, s->digits, max);
+        memmove(s->digits, s->digits + max, s->current_digits - max);
+        s->current_digits -= max;
+    }
+    buf[max] = '\0';
+    return  max;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(bell_mf_rx_state_t *) bell_mf_rx_init(bell_mf_rx_state_t *s,
+                                                   digits_rx_callback_t callback,
+                                                   void *user_data)
+{
+    int i;
+    static int initialised = FALSE;
+
+    if (s == NULL)
+    {
+        if ((s = (bell_mf_rx_state_t *) malloc(sizeof(*s))) == NULL)
+            return NULL;
+    }
+    memset(s, 0, sizeof(*s));
+
+    if (!initialised)
+    {
+        for (i = 0;  i < 6;  i++)
+            make_goertzel_descriptor(&bell_mf_detect_desc[i], (float) bell_mf_frequencies[i], BELL_MF_SAMPLES_PER_BLOCK);
+        initialised = TRUE;
+    }
+    s->digits_callback = callback;
+    s->digits_callback_data = user_data;
+
+    s->hits[0] = 
+    s->hits[1] =
+    s->hits[2] =
+    s->hits[3] = 
+    s->hits[4] = 0;
+
+    for (i = 0;  i < 6;  i++)
+        goertzel_init(&s->out[i], &bell_mf_detect_desc[i]);
+    s->current_sample = 0;
+    s->lost_digits = 0;
+    s->current_digits = 0;
+    s->digits[0] = '\0';
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) bell_mf_rx_release(bell_mf_rx_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) bell_mf_rx_free(bell_mf_rx_state_t *s)
+{
+    free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) r2_mf_rx(r2_mf_rx_state_t *s, const int16_t amp[], int samples)
+{
+#if defined(SPANDSP_USE_FIXED_POINT)
+    int32_t energy[6];
+    int16_t xamp;
+#else
+    float energy[6];
+    float xamp;
+#endif
+    int i;
+    int j;
+    int sample;
+    int best;
+    int second_best;
+    int hit;
+    int hit_digit;
+    int limit;
+
+    hit = 0;
+    hit_digit = 0;
+    for (sample = 0;  sample < samples;  sample = limit)
+    {
+        if ((samples - sample) >= (R2_MF_SAMPLES_PER_BLOCK - s->current_sample))
+            limit = sample + (R2_MF_SAMPLES_PER_BLOCK - s->current_sample);
+        else
+            limit = samples;
+        for (j = sample;  j < limit;  j++)
+        {
+            xamp = goertzel_preadjust_amp(amp[j]);
+            goertzel_samplex(&s->out[0], xamp);
+            goertzel_samplex(&s->out[1], xamp);
+            goertzel_samplex(&s->out[2], xamp);
+            goertzel_samplex(&s->out[3], xamp);
+            goertzel_samplex(&s->out[4], xamp);
+            goertzel_samplex(&s->out[5], xamp);
+        }
+        s->current_sample += (limit - sample);
+        if (s->current_sample < R2_MF_SAMPLES_PER_BLOCK)
+            continue;
+
+        /* We are at the end of an MF detection block */
+        /* Find the two highest energies */
+        energy[0] = goertzel_result(&s->out[0]);
+        energy[1] = goertzel_result(&s->out[1]);
+        if (energy[0] > energy[1])
+        {
+            best = 0;
+            second_best = 1;
+        }
+        else
+        {
+            best = 1;
+            second_best = 0;
+        }
+        
+        for (i = 2;  i < 6;  i++)
+        {
+            energy[i] = goertzel_result(&s->out[i]);
+            if (energy[i] >= energy[best])
+            {
+                second_best = best;
+                best = i;
+            }
+            else if (energy[i] >= energy[second_best])
+            {
+                second_best = i;
+            }
+        }
+        /* Basic signal level and twist tests */
+        hit = FALSE;
+        if (energy[best] >= R2_MF_THRESHOLD
+            &&
+            energy[second_best] >= R2_MF_THRESHOLD
+            &&
+            energy[best] < energy[second_best]*R2_MF_TWIST
+            &&
+            energy[best]*R2_MF_TWIST > energy[second_best])
+        {
+            /* Relative peak test */
+            hit = TRUE;
+            for (i = 0;  i < 6;  i++)
+            {
+                if (i != best  &&  i != second_best)
+                {
+                    if (energy[i]*R2_MF_RELATIVE_PEAK >= energy[second_best])
+                    {
+                        /* The best two are not clearly the best */
+                        hit = FALSE;
+                        break;
+                    }
+                }
+            }
+        }
+        if (hit)
+        {
+            /* Get the values into ascending order */
+            if (second_best < best)
+            {
+                i = best;
+                best = second_best;
+                second_best = i;
+            }
+            best = best*5 + second_best - 1;
+            hit_digit = r2_mf_positions[best];
+        }
+        else
+        {
+            hit_digit = 0;
+        }
+        if (s->current_digit != hit_digit  &&  s->callback)
+        {
+            i = (hit_digit)  ?  -99  :  -10;
+            s->callback(s->callback_data, hit_digit, i, 0);
+        }
+        s->current_digit = hit_digit;
+        s->current_sample = 0;
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) r2_mf_rx_get(r2_mf_rx_state_t *s)
+{
+    return s->current_digit;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(r2_mf_rx_state_t *) r2_mf_rx_init(r2_mf_rx_state_t *s,
+                                               int fwd,
+                                               tone_report_func_t callback,
+                                               void *user_data)
+{
+    int i;
+    static int initialised = FALSE;
+
+    if (s == NULL)
+    {
+        if ((s = (r2_mf_rx_state_t *) malloc(sizeof(*s))) == NULL)
+            return NULL;
+    }
+    memset(s, 0, sizeof(*s));
+
+    s->fwd = fwd;
+
+    if (!initialised)
+    {
+        for (i = 0;  i < 6;  i++)
+        {
+            make_goertzel_descriptor(&mf_fwd_detect_desc[i], (float) r2_mf_fwd_frequencies[i], R2_MF_SAMPLES_PER_BLOCK);
+            make_goertzel_descriptor(&mf_back_detect_desc[i], (float) r2_mf_back_frequencies[i], R2_MF_SAMPLES_PER_BLOCK);
+        }
+        initialised = TRUE;
+    }
+    if (fwd)
+    {
+        for (i = 0;  i < 6;  i++)
+            goertzel_init(&s->out[i], &mf_fwd_detect_desc[i]);
+    }
+    else
+    {
+        for (i = 0;  i < 6;  i++)
+            goertzel_init(&s->out[i], &mf_back_detect_desc[i]);
+    }
+    s->callback = callback;
+    s->callback_data = user_data;
+    s->current_digit = 0;
+    s->current_sample = 0;
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) r2_mf_rx_release(r2_mf_rx_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) r2_mf_rx_free(r2_mf_rx_state_t *s)
+{
+    free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/

Repositories maintained by Peter Meerwald, pmeerw@pmeerw.net.