diff spandsp-0.0.6pre17/src/v22bis_rx.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/v22bis_rx.c	Fri Jun 25 15:50:58 2010 +0200
@@ -0,0 +1,873 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * v22bis_rx.c - ITU V.22bis modem receive part
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2004 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: v22bis_rx.c,v 1.69 2009/11/04 15:52:06 steveu Exp $
+ */
+
+/*! \file */
+
+/* THIS IS A WORK IN PROGRESS - It is basically functional, but it is not feature
+   complete, and doesn't reliably sync over the signal and noise level ranges it
+   should. There are some nasty inefficiencies too!
+   TODO:
+        Better noise performance
+        Retrain is incomplete
+        Rate change is not implemented
+        Remote loopback is not implemented */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <inttypes.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.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/logging.h"
+#include "spandsp/complex.h"
+#include "spandsp/vector_float.h"
+#include "spandsp/complex_vector_float.h"
+#include "spandsp/async.h"
+#include "spandsp/power_meter.h"
+#include "spandsp/arctan2.h"
+#include "spandsp/dds.h"
+#include "spandsp/complex_filters.h"
+
+#include "spandsp/v29rx.h"
+#include "spandsp/v22bis.h"
+
+#include "spandsp/private/logging.h"
+#include "spandsp/private/v22bis.h"
+
+#if defined(SPANDSP_USE_FIXED_POINTx)
+#include "v22bis_rx_1200_floating_rrc.h"
+#include "v22bis_rx_2400_floating_rrc.h"
+#else
+#include "v22bis_rx_1200_floating_rrc.h"
+#include "v22bis_rx_2400_floating_rrc.h"
+#endif
+
+#define ms_to_symbols(t)        (((t)*600)/1000)
+
+/*! The adaption rate coefficient for the equalizer */
+#define EQUALIZER_DELTA         0.25f
+/*! The number of phase shifted coefficient set for the pulse shaping/bandpass filter */
+#define PULSESHAPER_COEFF_SETS  12
+
+/*
+The basic method used by the V.22bis receiver is:
+
+    Put each sample into the pulse-shaping and phase shift filter buffer
+
+    At T/2 rate:
+        Filter and demodulate the contents of the input filter buffer, producing a sample
+        in the equalizer filter buffer.
+
+        Tune the symbol timing based on the latest 3 samples in the equalizer buffer. This
+        updates the decision points for taking the T/2 samples.
+
+        Equalize the contents of the equalizer buffer, producing a demodulated constellation
+        point.
+
+        Find the nearest constellation point to the received position. This is our received
+        symbol.
+
+        Tune the local carrier, based on the angular mismatch between the actual signal and
+        the decision.
+        
+        Tune the equalizer, based on the mismatch between the actual signal and the decision.
+
+        Descramble and output the bits represented by the decision.
+*/
+
+static const uint8_t space_map_v22bis[6][6] =
+{
+    {11,  9,  9,  6,  6,  7},
+    {10,  8,  8,  4,  4,  5},
+    {10,  8,  8,  4,  4,  5},
+    {13, 12, 12,  0,  0,  2},
+    {13, 12, 12,  0,  0,  2},
+    {15, 14, 14,  1,  1,  3}
+};
+
+static const uint8_t phase_steps[4] =
+{
+    1, 0, 2, 3
+};
+
+static const uint8_t ones[] =
+{
+    0, 1, 1, 2,
+    1, 2, 2, 3,
+    1, 2, 2, 3,
+    2, 3, 3, 4
+};
+
+SPAN_DECLARE(float) v22bis_rx_carrier_frequency(v22bis_state_t *s)
+{
+    return dds_frequencyf(s->rx.carrier_phase_rate);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(float) v22bis_rx_symbol_timing_correction(v22bis_state_t *s)
+{
+    return (float) s->rx.total_baud_timing_correction/((float) PULSESHAPER_COEFF_SETS*40.0f/(3.0f*2.0f));
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(float) v22bis_rx_signal_power(v22bis_state_t *s)
+{
+    return power_meter_current_dbm0(&s->rx.rx_power) + 6.34f;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) v22bis_rx_signal_cutoff(v22bis_state_t *s, float cutoff)
+{
+    s->rx.carrier_on_power = (int32_t) (power_meter_level_dbm0(cutoff + 2.5f)*0.232f);
+    s->rx.carrier_off_power = (int32_t) (power_meter_level_dbm0(cutoff - 2.5f)*0.232f);
+}
+/*- End of function --------------------------------------------------------*/
+
+void v22bis_report_status_change(v22bis_state_t *s, int status)
+{
+    if (s->status_handler)
+        s->status_handler(s->status_user_data, status);
+    else if (s->put_bit)
+        s->put_bit(s->put_bit_user_data, status);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) v22bis_rx_equalizer_state(v22bis_state_t *s, complexf_t **coeffs)
+{
+    *coeffs = s->rx.eq_coeff;
+    return 2*V22BIS_EQUALIZER_LEN + 1;
+}
+/*- End of function --------------------------------------------------------*/
+
+void v22bis_equalizer_coefficient_reset(v22bis_state_t *s)
+{
+    /* Start with an equalizer based on everything being perfect */
+#if defined(SPANDSP_USE_FIXED_POINTx)
+    cvec_zeroi16(s->rx.eq_coeff, 2*V22BIS_EQUALIZER_LEN + 1);
+    s->rx.eq_coeff[V22BIS_EQUALIZER_LEN] = complex_seti16(3*FP_FACTOR, 0*FP_FACTOR);
+    s->rx.eq_delta = 32768.0f*EQUALIZER_DELTA/(2*V22BIS_EQUALIZER_LEN + 1);
+#else
+    cvec_zerof(s->rx.eq_coeff, 2*V22BIS_EQUALIZER_LEN + 1);
+    s->rx.eq_coeff[V22BIS_EQUALIZER_LEN] = complex_setf(3.0f, 0.0f);
+    s->rx.eq_delta = EQUALIZER_DELTA/(2*V22BIS_EQUALIZER_LEN + 1);
+#endif
+}
+/*- End of function --------------------------------------------------------*/
+
+static void equalizer_reset(v22bis_state_t *s)
+{
+    v22bis_equalizer_coefficient_reset(s);
+#if defined(SPANDSP_USE_FIXED_POINTx)
+    cvec_zeroi16(s->rx.eq_buf, V22BIS_EQUALIZER_MASK + 1);
+#else
+    cvec_zerof(s->rx.eq_buf, V22BIS_EQUALIZER_MASK + 1);
+#endif
+    s->rx.eq_put_step = 20 - 1;
+    s->rx.eq_step = 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static complexf_t equalizer_get(v22bis_state_t *s)
+{
+    int i;
+    int p;
+    complexf_t z;
+    complexf_t z1;
+
+    /* Get the next equalized value. */
+    z = complex_setf(0.0f, 0.0f);
+    p = s->rx.eq_step - 1;
+    for (i = 0;  i < 2*V22BIS_EQUALIZER_LEN + 1;  i++)
+    {
+        p = (p - 1) & V22BIS_EQUALIZER_MASK;
+        z1 = complex_mulf(&s->rx.eq_coeff[i], &s->rx.eq_buf[p]);
+        z = complex_addf(&z, &z1);
+    }
+    return z;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void tune_equalizer(v22bis_state_t *s, const complexf_t *z, const complexf_t *target)
+{
+    int i;
+    int p;
+    complexf_t ez;
+    complexf_t z1;
+
+    /* Find the x and y mismatch from the exact constellation position. */
+    ez = complex_subf(target, z);
+    ez.re *= s->rx.eq_delta;
+    ez.im *= s->rx.eq_delta;
+
+    p = s->rx.eq_step - 1;
+    for (i = 0;  i < 2*V22BIS_EQUALIZER_LEN + 1;  i++)
+    {
+        p = (p - 1) & V22BIS_EQUALIZER_MASK;
+        z1 = complex_conjf(&s->rx.eq_buf[p]);
+        z1 = complex_mulf(&ez, &z1);
+        s->rx.eq_coeff[i] = complex_addf(&s->rx.eq_coeff[i], &z1);
+        /* If we don't leak a little bit we seem to get some wandering adaption */
+        s->rx.eq_coeff[i].re *= 0.9999f;
+        s->rx.eq_coeff[i].im *= 0.9999f;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ void track_carrier(v22bis_state_t *s, const complexf_t *z, const complexf_t *target)
+{
+    float error;
+
+    /* For small errors the imaginary part of the difference between the actual and the target
+       positions is proportional to the phase error, for any particular target. However, the
+       different amplitudes of the various target positions scale things. */
+    error = z->im*target->re - z->re*target->im;
+    
+    s->rx.carrier_phase_rate += (int32_t) (s->rx.carrier_track_i*error);
+    s->rx.carrier_phase += (int32_t) (s->rx.carrier_track_p*error);
+    //span_log(&s->logging, SPAN_LOG_FLOW, "Im = %15.5f   f = %15.5f\n", error, dds_frequencyf(s->rx.carrier_phase_rate));
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ int descramble(v22bis_state_t *s, int bit)
+{
+    int out_bit;
+
+    bit &= 1;
+
+    /* Descramble the bit */
+    out_bit = (bit ^ (s->rx.scramble_reg >> 13) ^ (s->rx.scramble_reg >> 16)) & 1;
+    s->rx.scramble_reg = (s->rx.scramble_reg << 1) | bit;
+
+    if (s->rx.scrambler_pattern_count >= 64)
+    {
+        out_bit ^= 1;
+        s->rx.scrambler_pattern_count = 0;
+    }
+    if (bit)
+        s->rx.scrambler_pattern_count++;
+    else
+        s->rx.scrambler_pattern_count = 0;
+    return out_bit;
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ void put_bit(v22bis_state_t *s, int bit)
+{
+    int out_bit;
+
+    /* Descramble the bit */
+    out_bit = descramble(s, bit);
+    s->put_bit(s->put_bit_user_data, out_bit);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void decode_baud(v22bis_state_t *s, int nearest)
+{
+    int raw_bits;
+
+    raw_bits = phase_steps[((nearest >> 2) - (s->rx.constellation_state >> 2)) & 3];
+    s->rx.constellation_state = nearest;
+    /* The first two bits are the quadrant */
+    put_bit(s, raw_bits >> 1);
+    put_bit(s, raw_bits);
+    if (s->rx.sixteen_way_decisions)
+    {
+        /* The other two bits are the position within the quadrant */
+        put_bit(s, nearest >> 1);
+        put_bit(s, nearest);
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static int decode_baudx(v22bis_state_t *s, int nearest)
+{
+    int raw_bits;
+    int out_bits;
+
+    raw_bits = phase_steps[((nearest >> 2) - (s->rx.constellation_state >> 2)) & 3];
+    s->rx.constellation_state = nearest;
+    /* The first two bits are the quadrant */
+    out_bits = descramble(s, raw_bits >> 1);
+    out_bits = (out_bits << 1) | descramble(s, raw_bits);
+    if (s->rx.sixteen_way_decisions)
+    {
+        /* The other two bits are the position within the quadrant */
+        out_bits = (out_bits << 1) | descramble(s, nearest >> 1);
+        out_bits = (out_bits << 1) | descramble(s, nearest);
+    }
+    return out_bits;
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ void symbol_sync(v22bis_state_t *s)
+{
+    float p;
+    float q;
+    complexf_t zz;
+    complexf_t a;
+    complexf_t b;
+    complexf_t c;
+
+    /* This routine adapts the position of the half baud samples entering the equalizer. */
+
+    /* Perform a Gardner test for baud alignment on the three most recent samples. */
+    if (s->rx.sixteen_way_decisions)
+    {
+        p = s->rx.eq_buf[(s->rx.eq_step - 3) & V22BIS_EQUALIZER_MASK].re
+          - s->rx.eq_buf[(s->rx.eq_step - 1) & V22BIS_EQUALIZER_MASK].re;
+        p *= s->rx.eq_buf[(s->rx.eq_step - 2) & V22BIS_EQUALIZER_MASK].re;
+
+        q = s->rx.eq_buf[(s->rx.eq_step - 3) & V22BIS_EQUALIZER_MASK].im
+        - s->rx.eq_buf[(s->rx.eq_step - 1) & V22BIS_EQUALIZER_MASK].im;
+        q *= s->rx.eq_buf[(s->rx.eq_step - 2) & V22BIS_EQUALIZER_MASK].im;
+    }
+    else
+    {
+        /* Rotate the points to the 45 degree positions, to maximise the effectiveness of
+           the Gardner algorithm. This is particularly significant at the start of operation
+           to pull things in quickly. */
+        zz = complex_setf(0.894427, 0.44721f);
+        a = complex_mulf(&s->rx.eq_buf[(s->rx.eq_step - 3) & V22BIS_EQUALIZER_MASK], &zz);
+        b = complex_mulf(&s->rx.eq_buf[(s->rx.eq_step - 2) & V22BIS_EQUALIZER_MASK], &zz);
+        c = complex_mulf(&s->rx.eq_buf[(s->rx.eq_step - 1) & V22BIS_EQUALIZER_MASK], &zz);
+        p = (a.re - c.re)*b.re;
+        q = (a.im - c.im)*b.im;
+    }
+
+    s->rx.gardner_integrate += (p + q > 0.0f)  ?  s->rx.gardner_step  :  -s->rx.gardner_step;
+
+    if (abs(s->rx.gardner_integrate) >= 16)
+    {
+        /* This integrate and dump approach avoids rapid changes of the equalizer put step.
+           Rapid changes, without hysteresis, are bad. They degrade the equalizer performance
+           when the true symbol boundary is close to a sample boundary. */
+        s->rx.eq_put_step += (s->rx.gardner_integrate/16);
+        s->rx.total_baud_timing_correction += (s->rx.gardner_integrate/16);
+        //span_log(&s->logging, SPAN_LOG_FLOW, "Gardner kick %d [total %d]\n", s->rx.gardner_integrate, s->rx.total_baud_timing_correction);
+        if (s->rx.qam_report)
+            s->rx.qam_report(s->rx.qam_user_data, NULL, NULL, s->rx.gardner_integrate);
+        s->rx.gardner_integrate = 0;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static void process_half_baud(v22bis_state_t *s, const complexf_t *sample)
+{
+    complexf_t z;
+    complexf_t zz;
+    const complexf_t *target;
+    int re;
+    int im;
+    int nearest;
+    int bitstream;
+    int raw_bits;
+
+    z.re = sample->re;
+    z.im = sample->im;
+
+    /* Add a sample to the equalizer's circular buffer, but don't calculate anything
+       at this time. */
+    s->rx.eq_buf[s->rx.eq_step] = z;
+    s->rx.eq_step = (s->rx.eq_step + 1) & V22BIS_EQUALIZER_MASK;
+
+    /* On alternate insertions we have a whole baud and must process it. */
+    if ((s->rx.baud_phase ^= 1))
+        return;
+
+    symbol_sync(s);
+
+    z = equalizer_get(s);
+
+    /* Find the constellation point */
+    if (s->rx.sixteen_way_decisions)
+    {
+        re = (int) (z.re + 3.0f);
+        if (re > 5)
+            re = 5;
+        else if (re < 0)
+            re = 0;
+        im = (int) (z.im + 3.0f);
+        if (im > 5)
+            im = 5;
+        else if (im < 0)
+            im = 0;
+        nearest = space_map_v22bis[re][im];
+    }
+    else
+    {
+        /* Rotate to 45 degrees, to make the slicing trivial */
+        zz = complex_setf(0.894427, 0.44721f);
+        zz = complex_mulf(&z, &zz);
+        nearest = 0x01;
+        if (zz.re < 0.0f)
+            nearest |= 0x04;
+        if (zz.im < 0.0f)
+        {
+            nearest ^= 0x04;
+            nearest |= 0x08;
+        }
+    }
+    raw_bits = 0;
+
+    switch (s->rx.training)
+    {
+    case V22BIS_RX_TRAINING_STAGE_NORMAL_OPERATION:
+        /* Normal operation. */
+        target = &v22bis_constellation[nearest];
+        track_carrier(s, &z, target);
+        tune_equalizer(s, &z, target);
+        raw_bits = phase_steps[((nearest >> 2) - (s->rx.constellation_state >> 2)) & 3];
+        /* TODO: detect unscrambled ones indicating a loopback request */
+
+        /* Search for the S1 signal that might be requesting a retrain */
+        if ((s->rx.last_raw_bits ^ raw_bits) == 0x3)
+        {
+            s->rx.pattern_repeats++;
+        }
+        else
+        {
+            if (s->rx.pattern_repeats >= 50  &&  (s->rx.last_raw_bits == 0x3  ||  s->rx.last_raw_bits == 0x0))
+            {
+                /* We should get a full run of 00 11 (about 60 bauds) at either modem. */
+                span_log(&s->logging, SPAN_LOG_FLOW, "+++ S1 detected (%d long)\n", s->rx.pattern_repeats);
+                span_log(&s->logging, SPAN_LOG_FLOW, "+++ Accepting a retrain request\n");
+                s->rx.pattern_repeats = 0;
+                s->rx.training_count = 0;
+                s->rx.training = V22BIS_RX_TRAINING_STAGE_SCRAMBLED_ONES_AT_1200;
+                s->tx.training_count = 0;
+                s->tx.training = V22BIS_TX_TRAINING_STAGE_U0011;
+                v22bis_equalizer_coefficient_reset(s);
+                v22bis_report_status_change(s, SIG_STATUS_MODEM_RETRAIN_OCCURRED);
+            }
+            s->rx.pattern_repeats = 0;
+        }
+        decode_baud(s, nearest);
+        break;
+    case V22BIS_RX_TRAINING_STAGE_SYMBOL_ACQUISITION:
+        /* Allow time for the Gardner algorithm to settle the symbol timing. */
+        target = &z;
+        if (++s->rx.training_count >= 40)
+        {
+            /* QAM and Gardner only play nicely with heavy damping, so we need to change to
+               a slow rate of symbol timing adaption. However, it must not be so slow that it
+               cannot track the worst case timing error specified in V.22bis. This should be 0.01%,
+               but since we might be off in the opposite direction from the source, the total
+               error could be higher. */
+            s->rx.gardner_step = 4;
+            s->rx.pattern_repeats = 0;
+            if (s->calling_party)
+                s->rx.training = V22BIS_RX_TRAINING_STAGE_UNSCRAMBLED_ONES;
+            else
+                s->rx.training = V22BIS_RX_TRAINING_STAGE_SCRAMBLED_ONES_AT_1200;
+            /* Be pessimistic and see what the handshake brings */
+            s->negotiated_bit_rate = 1200;
+            break;
+        }
+        /* Once we have pulled in the symbol timing in a coarse way, use finer
+           steps to fine tune the timing. */
+        if (s->rx.training_count == 30)
+            s->rx.gardner_step = 32;
+        break;
+    case V22BIS_RX_TRAINING_STAGE_UNSCRAMBLED_ONES:
+        /* Calling modem only */
+        /* The calling modem should initially receive unscrambled ones at 1200bps */
+        target = &v22bis_constellation[nearest];
+        track_carrier(s, &z, target);
+        raw_bits = phase_steps[((nearest >> 2) - (s->rx.constellation_state >> 2)) & 3];
+        s->rx.constellation_state = nearest;
+        if (raw_bits != s->rx.last_raw_bits)
+            s->rx.pattern_repeats = 0;
+        else
+            s->rx.pattern_repeats++;
+        if (++s->rx.training_count == ms_to_symbols(155 + 456))
+        {
+            /* After the first 155ms things should have been steady, so check if the last 456ms was
+               steady at 11 or 00. */
+            if (raw_bits == s->rx.last_raw_bits
+                &&
+                (raw_bits == 0x3  ||  raw_bits == 0x0)
+                &&
+                s->rx.pattern_repeats >= ms_to_symbols(456))
+            {
+                /* It looks like the answering machine is sending us a clean unscrambled 11 or 00 */
+                if (s->bit_rate == 2400)
+                {
+                    /* Try to establish at 2400bps */
+                    span_log(&s->logging, SPAN_LOG_FLOW, "+++ starting U0011 (S1) (Caller)\n");
+                    s->tx.training = V22BIS_TX_TRAINING_STAGE_U0011;
+                    s->tx.training_count = 0;
+                }
+                else
+                {
+                    /* Only try to establish at 1200bps */
+                    span_log(&s->logging, SPAN_LOG_FLOW, "+++ starting S11 (1200) (Caller)\n");
+                    s->tx.training = V22BIS_TX_TRAINING_STAGE_S11;
+                    s->tx.training_count = 0;
+                }
+            }
+            s->rx.pattern_repeats = 0;
+            s->rx.training_count = 0;
+            s->rx.training = V22BIS_RX_TRAINING_STAGE_UNSCRAMBLED_ONES_SUSTAINING;
+        }
+        break;
+    case V22BIS_RX_TRAINING_STAGE_UNSCRAMBLED_ONES_SUSTAINING:
+        /* Calling modem only */
+        /* Wait for the end of the unscrambled ones at 1200bps */
+        target = &v22bis_constellation[nearest];
+        track_carrier(s, &z, target);
+        raw_bits = phase_steps[((nearest >> 2) - (s->rx.constellation_state >> 2)) & 3];
+        s->rx.constellation_state = nearest;
+        if (raw_bits != s->rx.last_raw_bits)
+        {
+            /* This looks like the end of the sustained initial unscrambled 11 or 00 */
+            s->tx.training_count = 0;
+            s->tx.training = V22BIS_TX_TRAINING_STAGE_TIMED_S11;
+            s->rx.training_count = 0;
+            s->rx.training = V22BIS_RX_TRAINING_STAGE_SCRAMBLED_ONES_AT_1200;
+            s->rx.pattern_repeats = 0;
+        }
+        break;
+    case V22BIS_RX_TRAINING_STAGE_SCRAMBLED_ONES_AT_1200:
+        target = &v22bis_constellation[nearest];
+        track_carrier(s, &z, target);
+        tune_equalizer(s, &z, target);
+        raw_bits = phase_steps[((nearest >> 2) - (s->rx.constellation_state >> 2)) & 3];
+        bitstream = decode_baudx(s, nearest);
+        s->rx.training_count++;
+//span_log(&s->logging, SPAN_LOG_FLOW, "S11 0x%02x 0x%02x 0x%X %d %d %d %d %d\n", raw_bits, nearest, bitstream, s->rx.scrambled_ones_to_date, 0, 0, 0, s->rx.training_count);
+        if (s->negotiated_bit_rate == 1200)
+        {
+            /* Search for the S1 signal */
+            if ((s->rx.last_raw_bits ^ raw_bits) == 0x3)
+            {
+                s->rx.pattern_repeats++;
+            }
+            else
+            {
+                if (s->rx.pattern_repeats >= 15  &&  (s->rx.last_raw_bits == 0x3  ||  s->rx.last_raw_bits == 0x0))
+                {
+                    /* We should get a full run of 00 11 (about 60 bauds) at the calling modem, but only about 20
+                       at the answering modem, as the first 40 are TED settling time. */
+                    span_log(&s->logging, SPAN_LOG_FLOW, "+++ S1 detected (%d long)\n", s->rx.pattern_repeats);
+                    if (s->bit_rate == 2400)
+                    {
+                        if (!s->calling_party)
+                        {
+                            /* Accept establishment at 2400bps */
+                            span_log(&s->logging, SPAN_LOG_FLOW, "+++ starting U0011 (S1) (Answerer)\n");
+                            s->tx.training = V22BIS_TX_TRAINING_STAGE_U0011;
+                            s->tx.training_count = 0;
+                        }
+                        s->negotiated_bit_rate = 2400;
+                    }
+                }
+                s->rx.pattern_repeats = 0;
+            }
+            if (s->rx.training_count >= ms_to_symbols(270))
+            {
+                /* If we haven't seen the S1 signal by now, we are committed to be in 1200bps mode */
+                if (s->calling_party)
+                {
+                    span_log(&s->logging, SPAN_LOG_FLOW, "+++ Rx normal operation (1200)\n");
+                    /* The transmit side needs to sustain the scrambled ones for a timed period */
+                    s->tx.training_count = 0;
+                    s->tx.training = V22BIS_TX_TRAINING_STAGE_TIMED_S11;
+                    /* Normal reception starts immediately */
+                    s->rx.training = V22BIS_RX_TRAINING_STAGE_NORMAL_OPERATION;
+                    s->rx.carrier_track_i = 8000.0f;
+                }
+                else
+                {
+                    span_log(&s->logging, SPAN_LOG_FLOW, "+++ starting S11 (1200) (Answerer)\n");
+                    /* The transmit side needs to sustain the scrambled ones for a timed period */
+                    s->tx.training_count = 0;
+                    s->tx.training = V22BIS_TX_TRAINING_STAGE_TIMED_S11;
+                    /* The receive side needs to wait a timed period, receiving scrambled ones,
+                       before entering normal operation. */
+                    s->rx.training = V22BIS_RX_TRAINING_STAGE_SCRAMBLED_ONES_AT_1200_SUSTAINING;
+                }
+            }
+        }
+        else
+        {
+            if (s->calling_party)
+            {
+                if (s->rx.training_count >= ms_to_symbols(100 + 450))
+                {
+                    span_log(&s->logging, SPAN_LOG_FLOW, "+++ starting 16 way decisions (caller)\n");
+                    s->rx.sixteen_way_decisions = TRUE;
+                    s->rx.training = V22BIS_RX_TRAINING_STAGE_WAIT_FOR_SCRAMBLED_ONES_AT_2400;
+                    s->rx.pattern_repeats = 0;
+                    s->rx.carrier_track_i = 8000.0f;
+                }
+            }
+            else
+            {
+                if (s->rx.training_count >= ms_to_symbols(450))
+                {
+                    span_log(&s->logging, SPAN_LOG_FLOW, "+++ starting 16 way decisions (answerer)\n");
+                    s->rx.sixteen_way_decisions = TRUE;
+                    s->rx.training = V22BIS_RX_TRAINING_STAGE_WAIT_FOR_SCRAMBLED_ONES_AT_2400;
+                    s->rx.pattern_repeats = 0;
+                }
+            }
+        }
+        break;
+    case V22BIS_RX_TRAINING_STAGE_SCRAMBLED_ONES_AT_1200_SUSTAINING:
+        target = &v22bis_constellation[nearest];
+        track_carrier(s, &z, target);
+        tune_equalizer(s, &z, target);
+        bitstream = decode_baudx(s, nearest);
+        if (++s->rx.training_count > ms_to_symbols(270 + 765))
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "+++ Rx normal operation (1200)\n");
+            s->rx.training = V22BIS_RX_TRAINING_STAGE_NORMAL_OPERATION;
+        }
+        break;
+    case V22BIS_RX_TRAINING_STAGE_WAIT_FOR_SCRAMBLED_ONES_AT_2400:
+        target = &v22bis_constellation[nearest];
+        track_carrier(s, &z, target);
+        tune_equalizer(s, &z, target);
+        bitstream = decode_baudx(s, nearest);
+        /* We need 32 sustained 1's to switch into normal operation. */
+        if (bitstream == 0xF)
+        {
+            if (++s->rx.pattern_repeats >= 9)
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "+++ Rx normal operation (2400)\n");
+                s->rx.training = V22BIS_RX_TRAINING_STAGE_NORMAL_OPERATION;
+            }
+        }
+        else
+        {
+            s->rx.pattern_repeats = 0;
+        }
+        break;
+    case V22BIS_RX_TRAINING_STAGE_PARKED:
+    default:
+        /* We failed to train! */
+        /* Park here until the carrier drops. */
+        target = &z;
+        break;
+    }
+    s->rx.last_raw_bits = raw_bits;
+    if (s->rx.qam_report)
+        s->rx.qam_report(s->rx.qam_user_data, &z, target, s->rx.constellation_state);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE_NONSTD(int) v22bis_rx(v22bis_state_t *s, const int16_t amp[], int len)
+{
+    int i;
+    int j;
+    int step;
+    complexf_t z;
+    complexf_t zz;
+    int32_t power;
+    complexf_t sample;
+    float ii;
+    float qq;
+
+    for (i = 0;  i < len;  i++)
+    {
+        /* Complex bandpass filter the signal, using a pair of FIRs, and RRC coeffs shifted
+           to centre at 1200Hz or 2400Hz. The filters support 12 fractional phase shifts, to 
+           permit signal extraction very close to the middle of a symbol. */
+        s->rx.rrc_filter[s->rx.rrc_filter_step] =
+        s->rx.rrc_filter[s->rx.rrc_filter_step + V22BIS_RX_FILTER_STEPS] = amp[i];
+        if (++s->rx.rrc_filter_step >= V22BIS_RX_FILTER_STEPS)
+            s->rx.rrc_filter_step = 0;
+
+        /* Calculate the I filter, with an arbitrary phase step, just so we can calculate
+           the signal power of the required carrier, with any guard tone or spillback of our
+           own transmitted signal suppressed. */
+        if (s->calling_party)
+        {
+            ii = rx_pulseshaper_2400_re[6][0]*s->rx.rrc_filter[s->rx.rrc_filter_step];
+            for (j = 1;  j < V22BIS_RX_FILTER_STEPS;  j++)
+                ii += rx_pulseshaper_2400_re[6][j]*s->rx.rrc_filter[j + s->rx.rrc_filter_step];
+        }
+        else
+        {
+            ii = rx_pulseshaper_1200_re[6][0]*s->rx.rrc_filter[s->rx.rrc_filter_step];
+            for (j = 1;  j < V22BIS_RX_FILTER_STEPS;  j++)
+                ii += rx_pulseshaper_1200_re[6][j]*s->rx.rrc_filter[j + s->rx.rrc_filter_step];
+        }
+        power = power_meter_update(&(s->rx.rx_power), (int16_t) ii);
+        if (s->rx.signal_present)
+        {
+            /* Look for power below the carrier off point */
+            if (power < s->rx.carrier_off_power)
+            {
+                v22bis_restart(s, s->bit_rate);
+                v22bis_report_status_change(s, SIG_STATUS_CARRIER_DOWN);
+                continue;
+            }
+        }
+        else
+        {
+            /* Look for power exceeding the carrier on point */
+            if (power < s->rx.carrier_on_power)
+                continue;
+            s->rx.signal_present = TRUE;
+            v22bis_report_status_change(s, SIG_STATUS_CARRIER_UP);
+        }
+        if (s->rx.training != V22BIS_RX_TRAINING_STAGE_PARKED)
+        {
+            /* Only spend effort processing this data if the modem is not
+               parked, after a training failure. */
+            z = dds_complexf(&s->rx.carrier_phase, s->rx.carrier_phase_rate);
+            if (s->rx.training == V22BIS_RX_TRAINING_STAGE_SYMBOL_ACQUISITION)
+            {
+                /* Only AGC during the initial symbol acquisition, and then lock the gain. */
+                s->rx.agc_scaling = 0.18f*3.60f/sqrtf(power);
+            }
+            /* Put things into the equalization buffer at T/2 rate. The Gardner algorithm
+               will fiddle the step to align this with the symbols. */
+            if ((s->rx.eq_put_step -= PULSESHAPER_COEFF_SETS) <= 0)
+            {
+                /* Pulse shape while still at the carrier frequency, using a quadrature
+                   pair of filters. This results in a properly bandpass filtered complex
+                   signal, which can be brought directly to bandband by complex mixing.
+                   No further filtering, to remove mixer harmonics, is needed. */
+                step = -s->rx.eq_put_step;
+                if (step > PULSESHAPER_COEFF_SETS - 1)
+                    step = PULSESHAPER_COEFF_SETS - 1;
+                s->rx.eq_put_step += PULSESHAPER_COEFF_SETS*40/(3*2);
+                if (s->calling_party)
+                {
+                    ii = rx_pulseshaper_2400_re[step][0]*s->rx.rrc_filter[s->rx.rrc_filter_step];
+                    qq = rx_pulseshaper_2400_im[step][0]*s->rx.rrc_filter[s->rx.rrc_filter_step];
+                    for (j = 1;  j < V22BIS_RX_FILTER_STEPS;  j++)
+                    {
+                        ii += rx_pulseshaper_2400_re[step][j]*s->rx.rrc_filter[j + s->rx.rrc_filter_step];
+                        qq += rx_pulseshaper_2400_im[step][j]*s->rx.rrc_filter[j + s->rx.rrc_filter_step];
+                    }
+                }
+                else
+                {
+                    ii = rx_pulseshaper_1200_re[step][0]*s->rx.rrc_filter[s->rx.rrc_filter_step];
+                    qq = rx_pulseshaper_1200_im[step][0]*s->rx.rrc_filter[s->rx.rrc_filter_step];
+                    for (j = 1;  j < V22BIS_RX_FILTER_STEPS;  j++)
+                    {
+                        ii += rx_pulseshaper_1200_re[step][j]*s->rx.rrc_filter[j + s->rx.rrc_filter_step];
+                        qq += rx_pulseshaper_1200_im[step][j]*s->rx.rrc_filter[j + s->rx.rrc_filter_step];
+                    }
+                }
+                sample.re = ii*s->rx.agc_scaling;
+                sample.im = qq*s->rx.agc_scaling;
+                /* Shift to baseband - since this is done in a full complex form, the
+                   result is clean, and requires no further filtering apart from the
+                   equalizer. */
+                zz.re = sample.re*z.re - sample.im*z.im;
+                zz.im = -sample.re*z.im - sample.im*z.re;
+                process_half_baud(s, &zz);
+            }
+        }
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) v22bis_rx_fillin(v22bis_state_t *s, int len)
+{
+    int i;
+
+    /* We want to sustain the current state (i.e carrier on<->carrier off), and
+       try to sustain the carrier phase. We should probably push the filters, as well */
+    span_log(&s->logging, SPAN_LOG_FLOW, "Fill-in %d samples\n", len);
+    if (!s->rx.signal_present)
+        return 0;
+    for (i = 0;  i < len;  i++)
+    {
+#if defined(SPANDSP_USE_FIXED_POINTx)
+        dds_advance(&s->rx.carrier_phase, s->rx.carrier_phase_rate);
+#else
+        dds_advancef(&s->rx.carrier_phase, s->rx.carrier_phase_rate);
+#endif
+    }
+    /* TODO: Advance the symbol phase the appropriate amount */
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+int v22bis_rx_restart(v22bis_state_t *s)
+{
+    vec_zerof(s->rx.rrc_filter, sizeof(s->rx.rrc_filter)/sizeof(s->rx.rrc_filter[0]));
+    s->rx.rrc_filter_step = 0;
+    s->rx.scramble_reg = 0;
+    s->rx.scrambler_pattern_count = 0;
+    s->rx.training = V22BIS_RX_TRAINING_STAGE_SYMBOL_ACQUISITION;
+    s->rx.training_count = 0;
+    s->rx.signal_present = FALSE;
+
+    s->rx.carrier_phase_rate = dds_phase_ratef((s->calling_party)  ?  2400.0f  :  1200.0f);
+    s->rx.carrier_phase = 0;
+    power_meter_init(&(s->rx.rx_power), 5);
+    v22bis_rx_signal_cutoff(s, -45.5f);
+    s->rx.agc_scaling = 0.0005f*0.025f;
+
+    s->rx.constellation_state = 0;
+    s->rx.sixteen_way_decisions = FALSE;
+
+    equalizer_reset(s);
+
+    s->rx.pattern_repeats = 0;
+    s->rx.last_raw_bits = 0;
+    s->rx.gardner_integrate = 0;
+    s->rx.gardner_step = 256;
+    s->rx.baud_phase = 0;
+    s->rx.training_error = 0.0f;
+    s->rx.total_baud_timing_correction = 0;
+    /* We want the carrier to pull in faster on the answerer side, as it has very little time to adapt. */
+    s->rx.carrier_track_i = (s->calling_party)  ?  8000.0f  :  40000.0f;
+    s->rx.carrier_track_p = 8000000.0f;
+
+    s->negotiated_bit_rate = 1200;
+
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) v22bis_rx_set_qam_report_handler(v22bis_state_t *s, qam_report_handler_t handler, void *user_data)
+{
+    s->rx.qam_report = handler;
+    s->rx.qam_user_data = user_data;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/

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