diff spandsp-0.0.6pre17/src/v17rx.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/v17rx.c	Fri Jun 25 15:50:58 2010 +0200
@@ -0,0 +1,1462 @@
+#define IAXMODEM_STUFF
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * v17rx.c - ITU V.17 modem receive part
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2004, 2005, 2006, 2007 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: v17rx.c,v 1.153.4.6 2009/12/28 12:20:46 steveu Exp $
+ */
+
+/*! \file */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdio.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/vector_int.h"
+#include "spandsp/complex_vector_int.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/v17tx.h"
+#include "spandsp/v17rx.h"
+
+#include "spandsp/private/logging.h"
+#include "spandsp/private/v17rx.h"
+
+#include "v17_v32bis_tx_constellation_maps.h"
+#include "v17_v32bis_rx_constellation_maps.h"
+#if defined(SPANDSP_USE_FIXED_POINT)
+#include "v17_v32bis_rx_fixed_rrc.h"
+#else
+#include "v17_v32bis_rx_floating_rrc.h"
+#endif
+
+/*! The nominal frequency of the carrier, in Hertz */
+#define CARRIER_NOMINAL_FREQ            1800.0f
+/*! The nominal baud or symbol rate */
+#define BAUD_RATE                       2400
+/*! The adaption rate coefficient for the equalizer during initial training */
+#define EQUALIZER_DELTA                 0.21f
+/*! The adaption rate coefficient for the equalizer during continuous fine tuning */
+#define EQUALIZER_SLOW_ADAPT_RATIO      0.1f
+
+/* Segments of the training sequence */
+/*! The length of training segment 1, in symbols */
+#define V17_TRAINING_SEG_1_LEN          256
+/*! The length of training segment 2 in long training mode, in symbols */
+#define V17_TRAINING_SEG_2_LEN          2976
+/*! The length of training segment 2 in short training mode, in symbols */
+#define V17_TRAINING_SHORT_SEG_2_LEN    38
+/*! The length of training segment 3, in symbols */
+#define V17_TRAINING_SEG_3_LEN          64
+/*! The length of training segment 4A, in symbols */
+#define V17_TRAINING_SEG_4A_LEN         15
+/*! The length of training segment 4, in symbols */
+#define V17_TRAINING_SEG_4_LEN          48
+
+/*! The 16 bit pattern used in the bridge section of the training sequence */
+#define V17_BRIDGE_WORD                 0x8880
+
+/*! The length of the equalizer buffer */
+#define V17_EQUALIZER_LEN    (V17_EQUALIZER_PRE_LEN + 1 + V17_EQUALIZER_POST_LEN)
+
+enum
+{
+    TRAINING_STAGE_NORMAL_OPERATION = 0,
+    TRAINING_STAGE_SYMBOL_ACQUISITION,
+    TRAINING_STAGE_LOG_PHASE,
+    TRAINING_STAGE_SHORT_WAIT_FOR_CDBA,
+    TRAINING_STAGE_WAIT_FOR_CDBA,
+    TRAINING_STAGE_COARSE_TRAIN_ON_CDBA,
+    TRAINING_STAGE_FINE_TRAIN_ON_CDBA,
+    TRAINING_STAGE_SHORT_TRAIN_ON_CDBA_AND_TEST,
+    TRAINING_STAGE_TRAIN_ON_CDBA_AND_TEST,
+    TRAINING_STAGE_BRIDGE,
+    TRAINING_STAGE_TCM_WINDUP,
+    TRAINING_STAGE_TEST_ONES,
+    TRAINING_STAGE_PARKED
+};
+
+/* Coefficients for the band edge symbol timing synchroniser (alpha = 0.99) */
+/* low_edge = 2.0f*M_PI*(CARRIER_NOMINAL_FREQ - BAUD_RATE/2.0f)/SAMPLE_RATE; */
+/* high_edge = 2.0f*M_PI*(CARRIER_NOMINAL_FREQ + BAUD_RATE/2.0f)/SAMPLE_RATE; */
+#define SIN_LOW_BAND_EDGE               0.453990499f
+#define COS_LOW_BAND_EDGE               0.891006542f
+#define SIN_HIGH_BAND_EDGE              0.707106781f
+#define COS_HIGH_BAND_EDGE             -0.707106781f
+#define ALPHA                           0.99f
+
+#if defined(SPANDSP_USE_FIXED_POINTx)
+#define SYNC_LOW_BAND_EDGE_COEFF_0      ((int)(FP_FACTOR*(2.0f*ALPHA*COS_LOW_BAND_EDGE)))
+#define SYNC_LOW_BAND_EDGE_COEFF_1      ((int)(FP_FACTOR*(-ALPHA*ALPHA)))
+#define SYNC_LOW_BAND_EDGE_COEFF_2      ((int)(FP_FACTOR*(-ALPHA*SIN_LOW_BAND_EDGE)))
+#define SYNC_HIGH_BAND_EDGE_COEFF_0     ((int)(FP_FACTOR*(2.0f*ALPHA*COS_HIGH_BAND_EDGE)))
+#define SYNC_HIGH_BAND_EDGE_COEFF_1     ((int)(FP_FACTOR*(-ALPHA*ALPHA)))
+#define SYNC_HIGH_BAND_EDGE_COEFF_2     ((int)(FP_FACTOR*(-ALPHA*SIN_HIGH_BAND_EDGE)))
+#define SYNC_MIXED_EDGES_COEFF_3        ((int)(FP_FACTOR*(-ALPHA*ALPHA*(SIN_HIGH_BAND_EDGE*COS_LOW_BAND_EDGE - SIN_LOW_BAND_EDGE*COS_HIGH_BAND_EDGE))))
+#else
+#define SYNC_LOW_BAND_EDGE_COEFF_0      (2.0f*ALPHA*COS_LOW_BAND_EDGE)
+#define SYNC_LOW_BAND_EDGE_COEFF_1      (-ALPHA*ALPHA)
+#define SYNC_LOW_BAND_EDGE_COEFF_2      (-ALPHA*SIN_LOW_BAND_EDGE)
+#define SYNC_HIGH_BAND_EDGE_COEFF_0     (2.0f*ALPHA*COS_HIGH_BAND_EDGE)
+#define SYNC_HIGH_BAND_EDGE_COEFF_1     (-ALPHA*ALPHA)
+#define SYNC_HIGH_BAND_EDGE_COEFF_2     (-ALPHA*SIN_HIGH_BAND_EDGE)
+#define SYNC_MIXED_EDGES_COEFF_3        (-ALPHA*ALPHA*(SIN_HIGH_BAND_EDGE*COS_LOW_BAND_EDGE - SIN_LOW_BAND_EDGE*COS_HIGH_BAND_EDGE))
+#endif
+
+#if defined(SPANDSP_USE_FIXED_POINTx)
+static const int constellation_spacing[4] =
+{
+    ((int)(FP_FACTOR*1.414f),
+    ((int)(FP_FACTOR*2.0f)},
+    ((int)(FP_FACTOR*2.828f)},
+    ((int)(FP_FACTOR*4.0f)},
+};
+#else
+static const float constellation_spacing[4] =
+{
+    1.414f,
+    2.0f,
+    2.828f,
+    4.0f
+};
+#endif
+
+SPAN_DECLARE(float) v17_rx_carrier_frequency(v17_rx_state_t *s)
+{
+    return dds_frequencyf(s->carrier_phase_rate);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(float) v17_rx_symbol_timing_correction(v17_rx_state_t *s)
+{
+    return (float) s->total_baud_timing_correction/((float) RX_PULSESHAPER_COEFF_SETS*10.0f/3.0f);
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(float) v17_rx_signal_power(v17_rx_state_t *s)
+{
+    return power_meter_current_dbm0(&s->power) + 3.98f;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) v17_rx_signal_cutoff(v17_rx_state_t *s, float cutoff)
+{
+    /* The 0.4 factor allows for the gain of the DC blocker */
+    s->carrier_on_power = (int32_t) (power_meter_level_dbm0(cutoff + 2.5f)*0.4f);
+    s->carrier_off_power = (int32_t) (power_meter_level_dbm0(cutoff - 2.5f)*0.4f);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void report_status_change(v17_rx_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 --------------------------------------------------------*/
+
+#if defined(SPANDSP_USE_FIXED_POINTx)
+SPAN_DECLARE(int) v17_rx_equalizer_state(v17_rx_state_t *s, complexi16_t **coeffs)
+#else
+SPAN_DECLARE(int) v17_rx_equalizer_state(v17_rx_state_t *s, complexf_t **coeffs)
+#endif
+{
+    *coeffs = s->eq_coeff;
+    return V17_EQUALIZER_LEN;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void equalizer_save(v17_rx_state_t *s)
+{
+#if defined(SPANDSP_USE_FIXED_POINTx)
+    cvec_copyi16(s->eq_coeff_save, s->eq_coeff, V17_EQUALIZER_LEN);
+#else
+    cvec_copyf(s->eq_coeff_save, s->eq_coeff, V17_EQUALIZER_LEN);
+#endif
+}
+/*- End of function --------------------------------------------------------*/
+
+static void equalizer_restore(v17_rx_state_t *s)
+{
+#if defined(SPANDSP_USE_FIXED_POINTx)
+    cvec_copyi16(s->eq_coeff, s->eq_coeff_save, V17_EQUALIZER_LEN);
+    cvec_zeroi16(s->eq_buf, V17_EQUALIZER_LEN);
+    s->eq_delta = 32768.0f*EQUALIZER_SLOW_ADAPT_RATIO*EQUALIZER_DELTA/V17_EQUALIZER_LEN;
+#else
+    cvec_copyf(s->eq_coeff, s->eq_coeff_save, V17_EQUALIZER_LEN);
+    cvec_zerof(s->eq_buf, V17_EQUALIZER_LEN);
+    s->eq_delta = EQUALIZER_SLOW_ADAPT_RATIO*EQUALIZER_DELTA/V17_EQUALIZER_LEN;
+#endif
+
+    s->eq_put_step = RX_PULSESHAPER_COEFF_SETS*10/(3*2) - 1;
+    s->eq_step = 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void equalizer_reset(v17_rx_state_t *s)
+{
+    /* Start with an equalizer based on everything being perfect */
+#if defined(SPANDSP_USE_FIXED_POINTx)
+    cvec_zeroi16(s->eq_coeff, V17_EQUALIZER_LEN);
+    s->eq_coeff[V17_EQUALIZER_PRE_LEN] = complex_seti16(3*FP_FACTOR, 0);
+    cvec_zeroi16(s->eq_buf, V17_EQUALIZER_LEN);
+    s->eq_delta = 32768.0f*EQUALIZER_DELTA/V17_EQUALIZER_LEN;
+#else
+    cvec_zerof(s->eq_coeff, V17_EQUALIZER_LEN);
+    s->eq_coeff[V17_EQUALIZER_PRE_LEN] = complex_setf(3.0f, 0.0f);
+    cvec_zerof(s->eq_buf, V17_EQUALIZER_LEN);
+    s->eq_delta = EQUALIZER_DELTA/V17_EQUALIZER_LEN;
+#endif
+
+    s->eq_put_step = RX_PULSESHAPER_COEFF_SETS*10/(3*2) - 1;
+    s->eq_step = 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+#if defined(SPANDSP_USE_FIXED_POINTx)
+static __inline__ complexi16_t equalizer_get(v17_rx_state_t *s)
+#else
+static __inline__ complexf_t equalizer_get(v17_rx_state_t *s)
+#endif
+{
+    return cvec_circular_dot_prodf(s->eq_buf, s->eq_coeff, V17_EQUALIZER_LEN, s->eq_step);
+}
+/*- End of function --------------------------------------------------------*/
+
+#if defined(SPANDSP_USE_FIXED_POINTx)
+static void tune_equalizer(v17_rx_state_t *s, const complexi16_t *z, const complexi16_t *target)
+{
+    complexi16_t err;
+
+    /* Find the x and y mismatch from the exact constellation position. */
+    err.re = target->re*FP_FACTOR - z->re;
+    err.im = target->im*FP_FACTOR - z->im;
+    //span_log(&s->logging, SPAN_LOG_FLOW, "Equalizer error %f\n", sqrt(err.re*err.re + err.im*err.im));
+    err.re = ((int32_t) err.re*(int32_t) s->eq_delta) >> 15;
+    err.im = ((int32_t) err.im*(int32_t) s->eq_delta) >> 15;
+    cvec_circular_lmsi16(s->eq_buf, s->eq_coeff, V17_EQUALIZER_LEN, s->eq_step, &err);
+}
+#else
+static void tune_equalizer(v17_rx_state_t *s, const complexf_t *z, const complexf_t *target)
+{
+    complexf_t err;
+
+    /* Find the x and y mismatch from the exact constellation position. */
+    err = complex_subf(target, z);
+    //span_log(&s->logging, SPAN_LOG_FLOW, "Equalizer error %f\n", sqrt(err.re*err.re + err.im*err.im));
+    err.re *= s->eq_delta;
+    err.im *= s->eq_delta;
+    cvec_circular_lmsf(s->eq_buf, s->eq_coeff, V17_EQUALIZER_LEN, s->eq_step, &err);
+}
+#endif
+
+static int descramble(v17_rx_state_t *s, int in_bit)
+{
+    int out_bit;
+
+    //out_bit = (in_bit ^ (s->scramble_reg >> s->scrambler_tap) ^ (s->scramble_reg >> (23 - 1))) & 1;
+    out_bit = (in_bit ^ (s->scramble_reg >> (18 - 1)) ^ (s->scramble_reg >> (23 - 1))) & 1;
+    s->scramble_reg <<= 1;
+    if (s->training_stage > TRAINING_STAGE_NORMAL_OPERATION  &&  s->training_stage < TRAINING_STAGE_TCM_WINDUP)
+        s->scramble_reg |= out_bit;
+    else
+        s->scramble_reg |= (in_bit & 1);
+    return out_bit;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void track_carrier(v17_rx_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->carrier_phase_rate += (int32_t) (s->carrier_track_i*error);
+    s->carrier_phase += (int32_t) (s->carrier_track_p*error);
+    //span_log(&s->logging, SPAN_LOG_FLOW, "Im = %15.5f   f = %15.5f\n", error, dds_frequencyf(s->carrier_phase_rate));
+    //printf("XXX Im = %15.5f   f = %15.5f   %f %f %f %f (%f %f)\n", error, dds_frequencyf(s->carrier_phase_rate), target->re, target->im, z->re, z->im, s->carrier_track_i, s->carrier_track_p);
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ void put_bit(v17_rx_state_t *s, int bit)
+{
+    int out_bit;
+
+    /* We need to strip the last part of the training - the test period of all 1s -
+       before we let data go to the application. */
+    if (s->training_stage == TRAINING_STAGE_NORMAL_OPERATION)
+    {
+        out_bit = descramble(s, bit);
+        s->put_bit(s->put_bit_user_data, out_bit);
+    }
+    else if (s->training_stage == TRAINING_STAGE_TEST_ONES)
+    {
+        /* The bits during the final stage of training should be all ones. However,
+           buggy modems mean you cannot rely on this. Therefore we don't bother
+           testing for ones, but just rely on a constellation mismatch measurement. */
+        out_bit = descramble(s, bit);
+        //span_log(&s->logging, SPAN_LOG_FLOW, "A 1 is really %d\n", out_bit);
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+#if defined(SPANDSP_USE_FIXED_POINTx)
+static __inline__ uint32_t dist_sq(const complexi_t *x, const complexi_t *y)
+{
+    return (x->re - y->re)*(x->re - y->re) + (x->im - y->im)*(x->im - y->im);
+}
+/*- End of function --------------------------------------------------------*/
+#else
+static __inline__ float dist_sq(const complexf_t *x, const complexf_t *y)
+{
+    return (x->re - y->re)*(x->re - y->re) + (x->im - y->im)*(x->im - y->im);
+}
+/*- End of function --------------------------------------------------------*/
+#endif
+
+static int decode_baud(v17_rx_state_t *s, complexf_t *z)
+{
+    static const uint8_t v32bis_4800_differential_decoder[4][4] =
+    {
+        {2, 3, 0, 1},
+        {0, 2, 1, 3},
+        {3, 1, 2, 0},
+        {1, 0, 3, 2}
+    };
+    static const uint8_t v17_differential_decoder[4][4] =
+    {
+        {0, 1, 2, 3},
+        {3, 0, 1, 2},
+        {2, 3, 0, 1},
+        {1, 2, 3, 0}
+    };
+    static const uint8_t tcm_paths[8][4] =
+    {
+        {0, 6, 2, 4},
+        {6, 0, 4, 2},
+        {2, 4, 0, 6},
+        {4, 2, 6, 0},
+        {1, 3, 7, 5},
+        {5, 7, 3, 1},
+        {7, 5, 1, 3},
+        {3, 1, 5, 7}
+    };
+    int nearest;
+    int i;
+    int j;
+    int k;
+    int re;
+    int im;
+    int raw;
+    int constellation_state;
+#if defined(SPANDSP_USE_FIXED_POINTx)
+#define DIST_FACTOR 2048       /* Something less than sqrt(0xFFFFFFFF/10)/10 */
+    complexi_t zi;
+    uint32_t distances[8];
+    uint32_t new_distances[8];
+    uint32_t min;
+    complexi_t ci;
+#else
+    float distances[8];
+    float new_distances[8];
+    float min;
+#endif
+
+    re = (int) ((z->re + 9.0f)*2.0f);
+    if (re > 35)
+        re = 35;
+    else if (re < 0)
+        re = 0;
+    im = (int) ((z->im + 9.0f)*2.0f);
+    if (im > 35)
+        im = 35;
+    else if (im < 0)
+        im = 0;
+
+    if (s->bits_per_symbol == 2)
+    {
+        /* 4800bps V.32bis mode, without trellis coding */
+        nearest = constel_map_4800[re][im];
+        raw = v32bis_4800_differential_decoder[s->diff][nearest];
+        s->diff = nearest;
+        put_bit(s, raw);
+        put_bit(s, raw >> 1);
+        return nearest;
+    }
+
+    /* Find a set of 8 candidate constellation positions, that are the closest
+       to the target, with different patterns in the last 3 bits. */
+#if defined(SPANDSP_USE_FIXED_POINTx)
+    min = 0xFFFFFFFF;
+    zi = complex_seti(z->re*DIST_FACTOR, z->im*DIST_FACTOR);
+#else
+    min = 9999999.0f;
+#endif
+    j = 0;
+    for (i = 0;  i < 8;  i++)
+    {
+        nearest = constel_maps[s->space_map][re][im][i];
+#if defined(SPANDSP_USE_FIXED_POINTx)
+        ci = complex_seti(s->constellation[nearest].re*DIST_FACTOR,
+                          s->constellation[nearest].im*DIST_FACTOR);
+        distances[i] = dist_sq(&ci, &zi);
+#else
+        distances[i] = dist_sq(&s->constellation[nearest], z);
+#endif
+        if (min > distances[i])
+        {
+            min = distances[i];
+            j = i;
+        }
+    }
+    /* Use the nearest of these soft-decisions as the basis for DFE */
+    constellation_state = constel_maps[s->space_map][re][im][j];
+    /* Control the equalizer, carrier tracking, etc. based on the non-trellis
+       corrected information. The trellis correct stuff comes out a bit late. */
+    track_carrier(s, z, &s->constellation[constellation_state]);
+    //tune_equalizer(s, z, &s->constellation[constellation_state]);
+
+    /* Now do the trellis decoding */
+
+    /* TODO: change to processing blocks of stored symbols here, instead of processing
+             one symbol at a time, to speed up the processing. */
+
+    /* Update the minimum accumulated distance to each of the 8 states */
+    if (++s->trellis_ptr >= V17_TRELLIS_STORAGE_DEPTH)
+        s->trellis_ptr = 0;
+    for (i = 0;  i < 4;  i++)
+    {
+        min = distances[tcm_paths[i][0]] + s->distances[0];
+        k = 0;
+        for (j = 1;  j < 4;  j++)
+        {
+            if (min > distances[tcm_paths[i][j]] + s->distances[j << 1])
+            {
+                min = distances[tcm_paths[i][j]] + s->distances[j << 1];
+                k = j;
+            }
+        }
+        /* Use an elementary IIR filter to track the distance to date. */
+#if defined(SPANDSP_USE_FIXED_POINTx)
+        new_distances[i] = s->distances[k << 1]*9/10 + distances[tcm_paths[i][k]]*1/10;
+#else
+        new_distances[i] = s->distances[k << 1]*0.9f + distances[tcm_paths[i][k]]*0.1f;
+#endif
+        s->full_path_to_past_state_locations[s->trellis_ptr][i] = constel_maps[s->space_map][re][im][tcm_paths[i][k]];
+        s->past_state_locations[s->trellis_ptr][i] = k << 1;
+    }
+    for (i = 4;  i < 8;  i++)
+    {
+        min = distances[tcm_paths[i][0]] + s->distances[1];
+        k = 0;
+        for (j = 1;  j < 4;  j++)
+        {
+            if (min > distances[tcm_paths[i][j]] + s->distances[(j << 1) + 1])
+            {
+                min = distances[tcm_paths[i][j]] + s->distances[(j << 1) + 1];
+                k = j;
+            }
+        }
+#if defined(SPANDSP_USE_FIXED_POINTx)
+        new_distances[i] = s->distances[(k << 1) + 1]*9/10 + distances[tcm_paths[i][k]]*1/10;
+#else
+        new_distances[i] = s->distances[(k << 1) + 1]*0.9f + distances[tcm_paths[i][k]]*0.1f;
+#endif
+        s->full_path_to_past_state_locations[s->trellis_ptr][i] = constel_maps[s->space_map][re][im][tcm_paths[i][k]];
+        s->past_state_locations[s->trellis_ptr][i] = (k << 1) + 1;
+    }
+    memcpy(s->distances, new_distances, sizeof(s->distances));
+
+    /* Find the minimum distance to date. This is the start of the path back to the result. */
+    min = s->distances[0];
+    k = 0;
+    for (i = 1;  i < 8;  i++)
+    {
+        if (min > s->distances[i])
+        {
+            min = s->distances[i];
+            k = i;
+        }
+    }
+    /* Trace back through every time step, starting with the current one, and find the
+       state from which the path came one step before. At the end of this search, the
+       last state found also points to the constellation point at that state. This is the
+       output of the trellis. */
+    for (i = 0, j = s->trellis_ptr;  i < V17_TRELLIS_LOOKBACK_DEPTH - 1;  i++)
+    {
+        k = s->past_state_locations[j][k];
+        if (--j < 0)
+            j = V17_TRELLIS_STORAGE_DEPTH - 1;
+    }
+    nearest = s->full_path_to_past_state_locations[j][k] >> 1;
+
+    /* Differentially decode */
+    raw = (nearest & 0x3C) | v17_differential_decoder[s->diff][nearest & 0x03];
+    s->diff = nearest & 0x03;
+    for (i = 0;  i < s->bits_per_symbol;  i++)
+    {
+        put_bit(s, raw);
+        raw >>= 1;
+    }
+    return constellation_state;
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ void symbol_sync(v17_rx_state_t *s)
+{
+    int i;
+#if defined(SPANDSP_USE_FIXED_POINTx)
+    int32_t v;
+    int32_t p;
+#else
+    float v;
+    float p;
+#endif
+
+    /* This routine adapts the position of the half baud samples entering the equalizer. */
+
+    /* This symbol sync scheme is based on the technique first described by Dominique Godard in
+        Passband Timing Recovery in an All-Digital Modem Receiver
+        IEEE TRANSACTIONS ON COMMUNICATIONS, VOL. COM-26, NO. 5, MAY 1978 */
+
+    /* This is slightly rearranged for figure 3b of the Godard paper, as this saves a couple of
+       maths operations */
+#if defined(SPANDSP_USE_FIXED_POINTx)
+    /* TODO: The scalings used here need more thorough evaluation, to see if overflows are possible. */
+    /* Cross correlate */
+    v = (((s->symbol_sync_low[1] >> 5)*(s->symbol_sync_high[0] >> 4)) >> 15)*SYNC_LOW_BAND_EDGE_COEFF_2
+      - (((s->symbol_sync_low[0] >> 5)*(s->symbol_sync_high[1] >> 4)) >> 15)*SYNC_HIGH_BAND_EDGE_COEFF_2
+      + (((s->symbol_sync_low[1] >> 5)*(s->symbol_sync_high[1] >> 4)) >> 15)*SYNC_MIXED_EDGES_COEFF_3;
+    /* Filter away any DC component */
+    p = v - s->symbol_sync_dc_filter[1];
+    s->symbol_sync_dc_filter[1] = s->symbol_sync_dc_filter[0];
+    s->symbol_sync_dc_filter[0] = v;
+    /* A little integration will now filter away much of the HF noise */
+    s->baud_phase -= p;
+    if (abs(s->baud_phase) > 100*FP_FACTOR)
+    {
+        if (s->baud_phase > 0)
+            i = (s->baud_phase > 1000*FP_FACTOR)  ?  15  :  1;
+        else
+            i = (s->baud_phase < -1000*FP_FACTOR)  ?  -15  :  -1;
+        //printf("v = %10.5f %5d - %f %f %d %d\n", v, i, p, s->baud_phase, s->total_baud_timing_correction);
+        s->eq_put_step += i;
+        s->total_baud_timing_correction += i;
+    }
+#else
+    /* Cross correlate */
+    v = s->symbol_sync_low[1]*s->symbol_sync_high[0]*SYNC_LOW_BAND_EDGE_COEFF_2
+      - s->symbol_sync_low[0]*s->symbol_sync_high[1]*SYNC_HIGH_BAND_EDGE_COEFF_2
+      + s->symbol_sync_low[1]*s->symbol_sync_high[1]*SYNC_MIXED_EDGES_COEFF_3;
+    /* Filter away any DC component  */
+    p = v - s->symbol_sync_dc_filter[1];
+    s->symbol_sync_dc_filter[1] = s->symbol_sync_dc_filter[0];
+    s->symbol_sync_dc_filter[0] = v;
+    /* A little integration will now filter away much of the HF noise */
+    s->baud_phase -= p;
+    if (fabsf(s->baud_phase) > 100.0f)
+    {
+        if (s->baud_phase > 0.0f)
+            i = (s->baud_phase > 1000.0f)  ?  15  :  1;
+        else
+            i = (s->baud_phase < -1000.0f)  ?  -15  :  -1;
+        //printf("v = %10.5f %5d - %f %f %d\n", v, i, p, s->baud_phase, s->total_baud_timing_correction);
+        s->eq_put_step += i;
+        s->total_baud_timing_correction += i;
+    }
+#endif
+}
+/*- End of function --------------------------------------------------------*/
+
+static void process_half_baud(v17_rx_state_t *s, const complexf_t *sample)
+{
+    static const complexf_t cdba[4] =
+    {
+        { 6.0f,  2.0f},
+        {-2.0f,  6.0f},
+        { 2.0f, -6.0f},
+        {-6.0f, -2.0f}
+    };
+    complexf_t z;
+    complexf_t zz;
+#if defined(SPANDSP_USE_FIXED_POINTx)
+    const complexi_t *target;
+    static const complexi16_t zero = {0, 0};
+#else
+    const complexf_t *target;
+    static const complexf_t zero = {0, 0};
+#endif
+    float p;
+    int bit;
+    int i;
+    int j;
+    int32_t angle;
+    int32_t ang;
+    int constellation_state;
+
+    /* This routine processes every half a baud, as we put things into the equalizer at the T/2 rate. */
+
+    /* Add a sample to the equalizer's circular buffer, but don't calculate anything at this time. */
+    s->eq_buf[s->eq_step] = *sample;
+    if (++s->eq_step >= V17_EQUALIZER_LEN)
+        s->eq_step = 0;
+
+    /* On alternate insertions we have a whole baud and must process it. */
+    if ((s->baud_half ^= 1))
+        return;
+
+    /* Symbol timing synchronisation */
+    symbol_sync(s);
+
+    z = equalizer_get(s);
+
+    constellation_state = 0;
+    switch (s->training_stage)
+    {
+    case TRAINING_STAGE_NORMAL_OPERATION:
+        /* Normal operation. */
+        constellation_state = decode_baud(s, &z);
+        target = &s->constellation[constellation_state];
+        break;
+    case TRAINING_STAGE_SYMBOL_ACQUISITION:
+        /* Allow time for the symbol synchronisation to settle the symbol timing. */
+        target = &zero;
+        if (++s->training_count >= 100)
+        {
+            /* Record the current phase angle */
+            s->angles[0] =
+            s->start_angles[0] = arctan2(z.im, z.re);
+            s->training_stage = TRAINING_STAGE_LOG_PHASE;
+            if (s->agc_scaling_save == 0.0f)
+                s->agc_scaling_save = s->agc_scaling;
+        }
+        break;
+    case TRAINING_STAGE_LOG_PHASE:
+        /* Record the current alternate phase angle */
+        target = &zero;
+        angle = arctan2(z.im, z.re);
+        s->training_count = 1;
+        if (s->short_train)
+        {
+            /* We should already know the accurate carrier frequency. All we need to sort
+               out is the phase. */
+            /* Check if we just saw A or B */
+            if ((uint32_t) (angle - s->start_angles[0]) < 0x80000000U)
+            {
+                angle = s->start_angles[0];
+                s->angles[0] = 0xC0000000 + 219937506;
+                s->angles[1] = 0x80000000 + 219937506;
+            }
+            else
+            {
+                s->angles[0] = 0x80000000 + 219937506;
+                s->angles[1] = 0xC0000000 + 219937506;
+            }
+            /* Make a step shift in the phase, to pull it into line. We need to rotate the equalizer
+               buffer, as well as the carrier phase, for this to play out nicely. */
+            /* angle is now the difference between where A is, and where it should be */
+            p = 3.14159f + angle*2.0f*3.14159f/(65536.0f*65536.0f) - 0.321751f;
+            span_log(&s->logging, SPAN_LOG_FLOW, "Spin (short) by %.5f rads\n", p);
+            zz = complex_setf(cosf(p), -sinf(p));
+            for (i = 0;  i < V17_EQUALIZER_LEN;  i++)
+                s->eq_buf[i] = complex_mulf(&s->eq_buf[i], &zz);
+            s->carrier_phase += (0x80000000 + angle - 219937506);
+
+            s->carrier_track_p = 500000.0f;
+
+            s->training_stage = TRAINING_STAGE_SHORT_WAIT_FOR_CDBA;
+        }
+        else
+        {
+            s->angles[1] =
+            s->start_angles[1] = angle;
+            s->training_stage = TRAINING_STAGE_WAIT_FOR_CDBA;
+        }
+        break;
+    case TRAINING_STAGE_WAIT_FOR_CDBA:
+        target = &zero;
+        angle = arctan2(z.im, z.re);
+        /* Look for the initial ABAB sequence to display a phase reversal, which will
+           signal the start of the scrambled CDBA segment */
+        ang = angle - s->angles[(s->training_count - 1) & 0xF];
+        s->angles[(s->training_count + 1) & 0xF] = angle;
+
+        /* Do a coarse frequency adjustment about half way through the reversals, as if we wait until
+           the end, we might have rotated too far to correct properly. */
+        if (s->training_count == 100)
+        {
+            i = s->training_count;
+            /* Avoid the possibility of a divide by zero */
+            if (i)
+            {
+                j = i & 0xF;
+                ang = (s->angles[j] - s->start_angles[0])/i
+                    + (s->angles[j | 0x1] - s->start_angles[1])/i;
+                s->carrier_phase_rate += 3*(ang/20);
+                //span_log(&s->logging, SPAN_LOG_FLOW, "Angles %x, %x, %x, %x, dist %d\n", s->angles[j], s->start_angles[0], s->angles[j | 0x1], s->start_angles[1], i);
+
+                s->start_angles[0] = s->angles[j];
+                s->start_angles[1] = s->angles[j | 0x1];
+            }
+            //span_log(&s->logging, SPAN_LOG_FLOW, "%d %d %d %d %d\n", s->angles[s->training_count & 0xF], s->start_angles[0], s->angles[(s->training_count | 0x1) & 0xF], s->start_angles[1], s->training_count);
+            span_log(&s->logging, SPAN_LOG_FLOW, "First coarse carrier frequency %7.2f (%d)\n", dds_frequencyf(s->carrier_phase_rate), s->training_count);
+
+        }
+        if ((ang > 0x40000000  ||  ang < -0x40000000)  &&  s->training_count >= 13)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "We seem to have a reversal at symbol %d\n", s->training_count);
+            /* We seem to have a phase reversal */
+            /* Slam the carrier frequency into line, based on the total phase drift over the last
+               section. Use the shift from the odd bits and the shift from the even bits to get
+               better jitter suppression. */
+            /* TODO: We are supposed to deal with frequancy errors up to +-8Hz. Over 200+
+                     symbols that is more than half a cycle. We get confused an do crazy things.
+                     We can only cope with errors up to 5Hz right now. We need to implement
+                     greater tolerance to be compliant, although it doesn't really matter much
+                     these days. */
+            /* Step back a few symbols so we don't get ISI distorting things. */
+            i = (s->training_count - 8) & ~1;
+            /* Avoid the possibility of a divide by zero */
+            if (i - 100 + 8)
+            {
+                j = i & 0xF;
+                ang = (s->angles[j] - s->start_angles[0])/(i - 100 + 8)
+                    + (s->angles[j | 0x1] - s->start_angles[1])/(i - 100 + 8);
+                s->carrier_phase_rate += 3*(ang/20);
+                span_log(&s->logging, SPAN_LOG_FLOW, "Angles %x, %x, %x, %x, dist %d\n", s->angles[j], s->start_angles[0], s->angles[j | 0x1], s->start_angles[1], i);
+            }
+            //span_log(&s->logging, SPAN_LOG_FLOW, "%d %d %d %d %d\n", s->angles[s->training_count & 0xF], s->start_angles[0], s->angles[(s->training_count | 0x1) & 0xF], s->start_angles[1], s->training_count);
+            span_log(&s->logging, SPAN_LOG_FLOW, "Second coarse carrier frequency %7.2f (%d)\n", dds_frequencyf(s->carrier_phase_rate), s->training_count);
+            /* Check if the carrier frequency is plausible */
+            if (s->carrier_phase_rate < dds_phase_ratef(CARRIER_NOMINAL_FREQ - 20.0f)
+                ||
+                s->carrier_phase_rate > dds_phase_ratef(CARRIER_NOMINAL_FREQ + 20.0f))
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "Training failed (sequence failed)\n");
+                /* Park this modem */
+                s->agc_scaling_save = 0.0f;
+                s->training_stage = TRAINING_STAGE_PARKED;
+                report_status_change(s, SIG_STATUS_TRAINING_FAILED);
+                break;
+            }
+
+            /* Make a step shift in the phase, to pull it into line. We need to rotate the equalizer buffer,
+               as well as the carrier phase, for this to play out nicely. */
+            /* angle is now the difference between where C is, and where it should be */
+            p = angle*2.0f*3.14159f/(65536.0f*65536.0f) - 0.321751f;
+            span_log(&s->logging, SPAN_LOG_FLOW, "Spin (long) by %.5f rads\n", p);
+            zz = complex_setf(cosf(p), -sinf(p));
+            for (i = 0;  i < V17_EQUALIZER_LEN;  i++)
+                s->eq_buf[i] = complex_mulf(&s->eq_buf[i], &zz);
+            s->carrier_phase += (angle - 219937506);
+
+            /* We have just seen the first symbol of the scrambled sequence, so skip it. */
+            bit = descramble(s, 1);
+            bit = (bit << 1) | descramble(s, 1);
+            target = &cdba[bit];
+            s->training_count = 1;
+            s->training_stage = TRAINING_STAGE_COARSE_TRAIN_ON_CDBA;
+            report_status_change(s, SIG_STATUS_TRAINING_IN_PROGRESS);
+            break;
+        }
+        if (++s->training_count > V17_TRAINING_SEG_1_LEN)
+        {
+            /* This is bogus. There are not this many bits in this section
+               of a real training sequence. Note that this might be TEP. */
+            span_log(&s->logging, SPAN_LOG_FLOW, "Training failed (sequence failed)\n");
+            /* Park this modem */
+            s->agc_scaling_save = 0.0f;
+            s->training_stage = TRAINING_STAGE_PARKED;
+            report_status_change(s, SIG_STATUS_TRAINING_FAILED);
+        }
+        break;
+    case TRAINING_STAGE_COARSE_TRAIN_ON_CDBA:
+        /* Train on the scrambled CDBA section. */
+        bit = descramble(s, 1);
+        bit = (bit << 1) | descramble(s, 1);
+        target = &cdba[bit];
+        track_carrier(s, &z, target);
+        tune_equalizer(s, &z, target);
+#if defined(IAXMODEM_STUFF)
+        zz = complex_subf(&z, target);
+        s->training_error = powerf(&zz);
+        if (++s->training_count == V17_TRAINING_SEG_2_LEN - 2000  ||  s->training_error < 1.0f  ||  s->training_error > 200.0f)
+#else
+        if (++s->training_count == V17_TRAINING_SEG_2_LEN - 2000)
+#endif
+        {
+            /* Now the equaliser adaption should be getting somewhere, slow it down, or it will never
+               tune very well on a noisy signal. */
+            s->eq_delta *= EQUALIZER_SLOW_ADAPT_RATIO;
+            s->carrier_track_i = 1000.0f;
+            s->training_stage = TRAINING_STAGE_FINE_TRAIN_ON_CDBA;
+        }
+        break;
+    case TRAINING_STAGE_FINE_TRAIN_ON_CDBA:
+        /* Train on the scrambled CDBA section. */
+        bit = descramble(s, 1);
+        bit = (bit << 1) | descramble(s, 1);
+        target = &cdba[bit];
+        /* By this point the training should be comming into focus. */
+        track_carrier(s, &z, target);
+        tune_equalizer(s, &z, target);
+        if (++s->training_count >= V17_TRAINING_SEG_2_LEN - 48)
+        {
+            s->training_error = 0.0f;
+            s->carrier_track_i = 100.0f;
+            s->carrier_track_p = 500000.0f;
+            s->training_stage = TRAINING_STAGE_TRAIN_ON_CDBA_AND_TEST;
+        }
+        break;
+    case TRAINING_STAGE_TRAIN_ON_CDBA_AND_TEST:
+        /* Continue training on the scrambled CDBA section, but measure the quality of training too. */
+        bit = descramble(s, 1);
+        bit = (bit << 1) | descramble(s, 1);
+        target = &cdba[bit];
+        //span_log(&s->logging, SPAN_LOG_FLOW, "%5d [%15.5f, %15.5f]     [%15.5f, %15.5f]\n", s->training_count, z.re, z.im, cdba[bit].re, cdba[bit].im);
+        /* We ignore the last few symbols because it seems some modems do not end this
+           part properly, and it throws things off. */
+        if (++s->training_count < V17_TRAINING_SEG_2_LEN - 20)
+        {
+            track_carrier(s, &z, target);
+            tune_equalizer(s, &z, target);
+            /* Measure the training error */
+            zz = complex_subf(&z, &cdba[bit]);
+            s->training_error += powerf(&zz);
+        }
+        else if (s->training_count >= V17_TRAINING_SEG_2_LEN)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "Long training error %f\n", s->training_error);
+            if (s->training_error < 20.0f*1.414f*constellation_spacing[s->space_map])
+            {
+                s->training_count = 0;
+                s->training_error = 0.0f;
+                s->training_stage = TRAINING_STAGE_BRIDGE;
+            }
+            else
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "Training failed (convergence failed)\n");
+                /* Park this modem */
+                s->agc_scaling_save = 0.0f;
+                s->training_stage = TRAINING_STAGE_PARKED;
+                report_status_change(s, SIG_STATUS_TRAINING_FAILED);
+            }
+        }
+        break;
+    case TRAINING_STAGE_BRIDGE:
+        descramble(s, V17_BRIDGE_WORD >> ((s->training_count & 0x7) << 1));
+        descramble(s, V17_BRIDGE_WORD >> (((s->training_count & 0x7) << 1) + 1));
+        target = &z;
+        if (++s->training_count >= V17_TRAINING_SEG_3_LEN)
+        {
+            s->training_count = 0;
+            s->training_error = 0.0f;
+            if (s->bits_per_symbol == 2)
+            {
+                /* Restart the differential decoder */
+                /* There is no trellis, so go straight to processing decoded data */
+                s->diff = (s->short_train)  ?  0  :  1;
+                s->training_stage = TRAINING_STAGE_TEST_ONES;
+            }
+            else
+            {
+                /* Wait for the trellis to wind up */
+                s->training_stage = TRAINING_STAGE_TCM_WINDUP;
+            }
+        }
+        break;
+    case TRAINING_STAGE_SHORT_WAIT_FOR_CDBA:
+        /* Look for the initial ABAB sequence to display a phase reversal, which will
+           signal the start of the scrambled CDBA segment */
+        angle = arctan2(z.im, z.re);
+        ang = angle - s->angles[s->training_count & 1];
+        if (ang > 0x40000000  ||  ang < -0x40000000)
+        {
+            /* We seem to have a phase reversal */
+            /* We have just seen the first symbol of the scrambled sequence, so skip it. */
+            bit = descramble(s, 1);
+            bit = (bit << 1) | descramble(s, 1);
+            target = &cdba[bit];
+            s->training_count = 1;
+            s->training_error = 0.0f;
+            s->training_stage = TRAINING_STAGE_SHORT_TRAIN_ON_CDBA_AND_TEST;
+            break;
+        }
+        target = &cdba[(s->training_count & 1) + 2];
+        track_carrier(s, &z, target);
+        if (++s->training_count > V17_TRAINING_SEG_1_LEN)
+        {
+            /* This is bogus. There are not this many bits in this section
+               of a real training sequence. Note that this might be TEP. */
+            span_log(&s->logging, SPAN_LOG_FLOW, "Training failed (sequence failed)\n");
+            /* Park this modem */
+            s->training_stage = TRAINING_STAGE_PARKED;
+            report_status_change(s, SIG_STATUS_TRAINING_FAILED);
+        }
+        break;
+    case TRAINING_STAGE_SHORT_TRAIN_ON_CDBA_AND_TEST:
+        /* Short retrain on the scrambled CDBA section, but measure the quality of training too. */
+        bit = descramble(s, 1);
+        bit = (bit << 1) | descramble(s, 1);
+        //span_log(&s->logging, SPAN_LOG_FLOW, "%5d [%15.5f, %15.5f]     [%15.5f, %15.5f] %d\n", s->training_count, z.re, z.im, cdba[bit].re, cdba[bit].im, arctan2(z.im, z.re));
+        target = &cdba[bit];
+        track_carrier(s, &z, target);
+        //tune_equalizer(s, &z, target);
+        /* Measure the training error */
+        if (s->training_count > 8)
+        {
+            zz = complex_subf(&z, &cdba[bit]);
+            s->training_error += powerf(&zz);
+        }
+        if (++s->training_count >= V17_TRAINING_SHORT_SEG_2_LEN)
+        {
+            span_log(&s->logging, SPAN_LOG_FLOW, "Short training error %f\n", s->training_error);
+            s->carrier_track_i = 100.0f;
+            s->carrier_track_p = 500000.0f;
+            /* TODO: This was increased by a factor of 10 after studying real world failures.
+                     However, it is not clear why this is an improvement, If something gives
+                     a huge training error, surely it shouldn't decode too well? */
+            if (s->training_error < (V17_TRAINING_SHORT_SEG_2_LEN - 8)*4.0f*constellation_spacing[s->space_map])
+            {
+                s->training_count = 0;
+                if (s->bits_per_symbol == 2)
+                {
+                    /* There is no trellis, so go straight to processing decoded data */
+                    /* Restart the differential decoder */
+                    s->diff = (s->short_train)  ?  0  :  1;
+                    s->training_error = 0.0f;
+                    s->training_stage = TRAINING_STAGE_TEST_ONES;
+                }
+                else
+                {
+                    /* Wait for the trellis to wind up */
+                    s->training_stage = TRAINING_STAGE_TCM_WINDUP;
+                }
+                report_status_change(s, SIG_STATUS_TRAINING_IN_PROGRESS);
+            }
+            else
+            {
+                span_log(&s->logging, SPAN_LOG_FLOW, "Short training failed (convergence failed)\n");
+                /* Park this modem */
+                s->training_stage = TRAINING_STAGE_PARKED;
+                report_status_change(s, SIG_STATUS_TRAINING_FAILED);
+            }
+        }
+        break;
+    case TRAINING_STAGE_TCM_WINDUP:
+        /* We need to wait 15 bauds while the trellis fills up. */
+        //span_log(&s->logging, SPAN_LOG_FLOW, "%5d %15.5f, %15.5f\n", s->training_count, z.re, z.im);
+        constellation_state = decode_baud(s, &z);
+        target = &s->constellation[constellation_state];
+        /* Measure the training error */
+        zz = complex_subf(&z, target);
+        s->training_error += powerf(&zz);
+        if (++s->training_count >= V17_TRAINING_SEG_4A_LEN)
+        {
+            s->training_count = 0;
+            s->training_error = 0.0f;
+            /* Restart the differential decoder */
+            s->diff = (s->short_train)  ?  0  :  1;
+            s->training_stage = TRAINING_STAGE_TEST_ONES;
+        }
+        break;
+    case TRAINING_STAGE_TEST_ONES:
+        /* We are in the test phase, where we check that we can receive reliably.
+           We should get a run of 1's, 48 symbols long. */
+        //span_log(&s->logging, SPAN_LOG_FLOW, "%5d %15.5f, %15.5f\n", s->training_count, z.re, z.im);
+        constellation_state = decode_baud(s, &z);
+        target = &s->constellation[constellation_state];
+        /* Measure the training error */
+        zz = complex_subf(&z, target);
+        s->training_error += powerf(&zz);
+        if (++s->training_count >= V17_TRAINING_SEG_4_LEN)
+        {
+            if (s->training_error < V17_TRAINING_SEG_4_LEN*constellation_spacing[s->space_map])
+            {
+                /* We are up and running */
+                span_log(&s->logging, SPAN_LOG_FLOW, "Training succeeded at %dbps (constellation mismatch %f)\n", s->bit_rate, s->training_error);
+                report_status_change(s, SIG_STATUS_TRAINING_SUCCEEDED);
+                /* Apply some lag to the carrier off condition, to ensure the last few bits get pushed through
+                   the processing. */
+                s->signal_present = 60;
+                equalizer_save(s);
+                s->carrier_phase_rate_save = s->carrier_phase_rate;
+                s->short_train = TRUE;
+                s->training_stage = TRAINING_STAGE_NORMAL_OPERATION;
+            }
+            else
+            {
+                /* Training has failed */
+                span_log(&s->logging, SPAN_LOG_FLOW, "Training failed (constellation mismatch %f)\n", s->training_error);
+                /* Park this modem */
+                if (!s->short_train)
+                    s->agc_scaling_save = 0.0f;
+                s->training_stage = TRAINING_STAGE_PARKED;
+                report_status_change(s, SIG_STATUS_TRAINING_FAILED);
+            }
+        }
+        break;
+    case TRAINING_STAGE_PARKED:
+    default:
+        /* We failed to train! */
+        /* Park here until the carrier drops. */
+        target = &zero;
+        break;
+    }
+    if (s->qam_report)
+        s->qam_report(s->qam_user_data, &z, target, constellation_state);
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ int signal_detect(v17_rx_state_t *s, int16_t amp)
+{
+    int16_t diff;
+    int16_t x;
+    int32_t power;
+
+    /* There should be no DC in the signal, but sometimes there is.
+       We need to measure the power with the DC blocked, but not using
+       a slow to respond DC blocker. Use the most elementary HPF. */
+    x = amp >> 1;
+    /* There could be overflow here, but it isn't a problem in practice */
+    diff = x - s->last_sample;
+    s->last_sample = x;
+    power = power_meter_update(&(s->power), diff);
+#if defined(IAXMODEM_STUFF)
+    /* Quick power drop fudge */
+    diff = abs(diff);
+    if (10*diff < s->high_sample)
+    {
+        if (++s->low_samples > 120)
+        {
+            power_meter_init(&(s->power), 4);
+            s->high_sample = 0;
+            s->low_samples = 0;
+        }
+    }
+    else
+    { 
+        s->low_samples = 0;
+        if (diff > s->high_sample)
+            s->high_sample = diff;
+    }
+#endif
+    if (s->signal_present > 0)
+    {
+        /* Look for power below turn-off threshold to turn the carrier off */
+#if defined(IAXMODEM_STUFF)
+        if (s->carrier_drop_pending  ||  power < s->carrier_off_power)
+#else
+        if (power < s->carrier_off_power)
+#endif
+        {
+            if (--s->signal_present <= 0)
+            {
+                /* Count down a short delay, to ensure we push the last
+                   few bits through the filters before stopping. */
+                v17_rx_restart(s, s->bit_rate, s->short_train);
+                report_status_change(s, SIG_STATUS_CARRIER_DOWN);
+                return 0;
+            }
+#if defined(IAXMODEM_STUFF)
+            /* Carrier has dropped, but the put_bit is pending the signal_present delay. */
+            s->carrier_drop_pending = TRUE;
+#endif
+        }
+    }
+    else
+    {
+        /* Look for power exceeding turn-on threshold to turn the carrier on */
+        if (power < s->carrier_on_power)
+            return 0;
+        s->signal_present = 1;
+#if defined(IAXMODEM_STUFF)
+        s->carrier_drop_pending = FALSE;
+#endif
+        report_status_change(s, SIG_STATUS_CARRIER_UP);
+    }
+    return power;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE_NONSTD(int) v17_rx(v17_rx_state_t *s, const int16_t amp[], int len)
+{
+    int i;
+    int step;
+    complexf_t z;
+    complexf_t zz;
+    complexf_t sample;
+#if defined(SPANDSP_USE_FIXED_POINT)
+    int32_t vi;
+#endif
+#if defined(SPANDSP_USE_FIXED_POINTx)
+    int32_t v;
+#else
+    float v;
+#endif
+    int32_t power;
+
+    for (i = 0;  i < len;  i++)
+    {
+        s->rrc_filter[s->rrc_filter_step] = amp[i];
+        if (++s->rrc_filter_step >= V17_RX_FILTER_STEPS)
+            s->rrc_filter_step = 0;
+
+        if ((power = signal_detect(s, amp[i])) == 0)
+            continue;
+        if (s->training_stage == TRAINING_STAGE_PARKED)
+            continue;
+        /* Only spend effort processing this data if the modem is not
+           parked, after training failure. */
+        s->eq_put_step -= RX_PULSESHAPER_COEFF_SETS;
+        step = -s->eq_put_step;
+        if (step > RX_PULSESHAPER_COEFF_SETS - 1)
+            step = RX_PULSESHAPER_COEFF_SETS - 1;
+        if (step < 0)
+            step += RX_PULSESHAPER_COEFF_SETS;
+#if defined(SPANDSP_USE_FIXED_POINT)
+        vi = vec_circular_dot_prodi16(s->rrc_filter, rx_pulseshaper_re[step], V17_RX_FILTER_STEPS, s->rrc_filter_step);
+        //sample.re = (vi*(int32_t) s->agc_scaling) >> 15;
+        sample.re = vi*s->agc_scaling;
+#else
+        v = vec_circular_dot_prodf(s->rrc_filter, rx_pulseshaper_re[step], V17_RX_FILTER_STEPS, s->rrc_filter_step);
+        sample.re = v*s->agc_scaling;
+#endif
+        /* Symbol timing synchronisation band edge filters */
+        /* Low Nyquist band edge filter */
+        v = s->symbol_sync_low[0]*SYNC_LOW_BAND_EDGE_COEFF_0 + s->symbol_sync_low[1]*SYNC_LOW_BAND_EDGE_COEFF_1 + sample.re;
+        s->symbol_sync_low[1] = s->symbol_sync_low[0];
+        s->symbol_sync_low[0] = v;
+        /* High Nyquist band edge filter */
+        v = s->symbol_sync_high[0]*SYNC_HIGH_BAND_EDGE_COEFF_0 + s->symbol_sync_high[1]*SYNC_HIGH_BAND_EDGE_COEFF_1 + sample.re;
+        s->symbol_sync_high[1] = s->symbol_sync_high[0];
+        s->symbol_sync_high[0] = v;
+
+        /* Put things into the equalization buffer at T/2 rate. The symbol sync.
+           will fiddle the step to align this with the symbols. */
+        if (s->eq_put_step <= 0)
+        {
+            /* Only AGC until we have locked down the setting. */
+            if (s->agc_scaling_save == 0.0f)
+                s->agc_scaling = (1.0f/RX_PULSESHAPER_GAIN)*2.17f/sqrtf(power);
+            /* 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 baseband by complex mixing.
+               No further filtering, to remove mixer harmonics, is needed. */
+            step = -s->eq_put_step;
+            if (step > RX_PULSESHAPER_COEFF_SETS - 1)
+                step = RX_PULSESHAPER_COEFF_SETS - 1;
+            s->eq_put_step += RX_PULSESHAPER_COEFF_SETS*10/(3*2);
+#if defined(SPANDSP_USE_FIXED_POINT)
+            vi = vec_circular_dot_prodi16(s->rrc_filter, rx_pulseshaper_im[step], V17_RX_FILTER_STEPS, s->rrc_filter_step);
+            //sample.im = (vi*(int32_t) s->agc_scaling) >> 15;
+            sample.im = vi*s->agc_scaling;
+            z = dds_lookup_complexf(s->carrier_phase);
+            zz.re = sample.re*z.re - sample.im*z.im;
+            zz.im = -sample.re*z.im - sample.im*z.re;
+#else
+            v = vec_circular_dot_prodf(s->rrc_filter, rx_pulseshaper_im[step], V17_RX_FILTER_STEPS, s->rrc_filter_step);
+            sample.im = v*s->agc_scaling;
+            z = dds_lookup_complexf(s->carrier_phase);
+            zz.re = sample.re*z.re - sample.im*z.im;
+            zz.im = -sample.re*z.im - sample.im*z.re;
+#endif
+            process_half_baud(s, &zz);
+        }
+#if defined(SPANDSP_USE_FIXED_POINT)
+        dds_advance(&s->carrier_phase, s->carrier_phase_rate);
+#else
+        dds_advancef(&s->carrier_phase, s->carrier_phase_rate);
+#endif
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) v17_rx_fillin(v17_rx_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->signal_present <= 0)
+        return 0;
+    if (s->training_stage == TRAINING_STAGE_PARKED)
+        return 0;
+    for (i = 0;  i < len;  i++)
+    {
+#if defined(SPANDSP_USE_FIXED_POINT)
+        dds_advance(&s->carrier_phase, s->carrier_phase_rate);
+#else
+        dds_advancef(&s->carrier_phase, s->carrier_phase_rate);
+#endif
+        /* Advance the symbol phase the appropriate amount */
+        s->eq_put_step -= RX_PULSESHAPER_COEFF_SETS;
+        if (s->eq_put_step <= 0)
+            s->eq_put_step += RX_PULSESHAPER_COEFF_SETS*10/(3*2);
+        /* TODO: Should we rotate any buffers */
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) v17_rx_set_put_bit(v17_rx_state_t *s, put_bit_func_t put_bit, void *user_data)
+{
+    s->put_bit = put_bit;
+    s->put_bit_user_data = user_data;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) v17_rx_set_modem_status_handler(v17_rx_state_t *s, modem_tx_status_func_t handler, void *user_data)
+{
+    s->status_handler = handler;
+    s->status_user_data = user_data;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(logging_state_t *) v17_rx_get_logging_state(v17_rx_state_t *s)
+{
+    return &s->logging;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) v17_rx_restart(v17_rx_state_t *s, int bit_rate, int short_train)
+{
+    int i;
+
+    span_log(&s->logging, SPAN_LOG_FLOW, "Restarting V.17, %dbps, %s training\n", bit_rate, (short_train)  ?  "short"  :  "long");
+    switch (bit_rate)
+    {
+    case 14400:
+        s->constellation = v17_v32bis_14400_constellation;
+        s->space_map = 0;
+        s->bits_per_symbol = 6;
+        break;
+    case 12000:
+        s->constellation = v17_v32bis_12000_constellation;
+        s->space_map = 1;
+        s->bits_per_symbol = 5;
+        break;
+    case 9600:
+        s->constellation = v17_v32bis_9600_constellation;
+        s->space_map = 2;
+        s->bits_per_symbol = 4;
+        break;
+    case 7200:
+        s->constellation = v17_v32bis_7200_constellation;
+        s->space_map = 3;
+        s->bits_per_symbol = 3;
+        break;
+    case 4800:
+        /* This does not exist in the V.17 spec as a valid mode of operation.
+           However, it does exist in V.32bis, so it is here for completeness. */
+        s->constellation = v17_v32bis_4800_constellation;
+        s->space_map = 0;
+        s->bits_per_symbol = 2;
+        break;
+    default:
+        return -1;
+    }
+    s->bit_rate = bit_rate;
+#if defined(SPANDSP_USE_FIXED_POINT)
+    vec_zeroi16(s->rrc_filter, sizeof(s->rrc_filter)/sizeof(s->rrc_filter[0]));
+#else
+    vec_zerof(s->rrc_filter, sizeof(s->rrc_filter)/sizeof(s->rrc_filter[0]));
+#endif
+    s->rrc_filter_step = 0;
+
+    s->diff = 1;
+    s->scramble_reg = 0x2ECDD5;
+    s->training_stage = TRAINING_STAGE_SYMBOL_ACQUISITION;
+    s->training_count = 0;
+    s->training_error = 0.0f;
+    s->signal_present = 0;
+#if defined(IAXMODEM_STUFF)
+    s->high_sample = 0;
+    s->low_samples = 0;
+    s->carrier_drop_pending = FALSE;
+#endif
+    if (short_train != 2)
+        s->short_train = short_train;
+    memset(s->start_angles, 0, sizeof(s->start_angles));
+    memset(s->angles, 0, sizeof(s->angles));
+
+    /* Initialise the TCM decoder parameters. */
+    /* The accumulated distance vectors are set so state zero starts
+       at a value of zero, and all others start larger. This forces the
+       initial paths to merge at the zero states. */
+    for (i = 0;  i < 8;  i++)
+#if defined(SPANDSP_USE_FIXED_POINTx)
+        s->distances[i] = 99*DIST_FACTOR*DIST_FACTOR;
+#else
+        s->distances[i] = 99.0f;
+#endif
+    memset(s->full_path_to_past_state_locations, 0, sizeof(s->full_path_to_past_state_locations));
+    memset(s->past_state_locations, 0, sizeof(s->past_state_locations));
+    s->distances[0] = 0;
+    s->trellis_ptr = 14;
+
+    span_log(&s->logging, SPAN_LOG_FLOW, "Phase rates %f %f\n", dds_frequencyf(s->carrier_phase_rate), dds_frequencyf(s->carrier_phase_rate_save));
+    s->carrier_phase = 0;
+    power_meter_init(&(s->power), 4);
+
+    if (s->short_train)
+    {
+        s->carrier_phase_rate = s->carrier_phase_rate_save;
+        s->agc_scaling = s->agc_scaling_save;
+        equalizer_restore(s);
+        /* Don't allow any frequency correction at all, until we start to pull the phase in. */
+#if defined(SPANDSP_USE_FIXED_POINTx)
+        s->carrier_track_i = 0;
+        s->carrier_track_p = 40000;
+#else
+        s->carrier_track_i = 0.0f;
+        s->carrier_track_p = 40000.0f;
+#endif
+    }
+    else
+    {
+        s->carrier_phase_rate = dds_phase_ratef(CARRIER_NOMINAL_FREQ);
+        equalizer_reset(s);
+#if defined(SPANDSP_USE_FIXED_POINTx)
+        s->agc_scaling_save = 0;
+        s->agc_scaling = (float) FP_FACTOR*32768.0f*0.0017f/RX_PULSESHAPER_GAIN;
+        s->carrier_track_i = 5000;
+        s->carrier_track_p = 40000;
+#else
+        s->agc_scaling_save = 0.0f;
+        s->agc_scaling = 0.0017f/RX_PULSESHAPER_GAIN;
+        s->carrier_track_i = 5000.0f;
+        s->carrier_track_p = 40000.0f;
+#endif
+    }
+    s->last_sample = 0;
+
+    /* Initialise the working data for symbol timing synchronisation */
+#if defined(SPANDSP_USE_FIXED_POINTx)
+    for (i = 0;  i < 2;  i++)
+    {
+        s->symbol_sync_low[i] = 0;
+        s->symbol_sync_high[i] = 0;
+        s->symbol_sync_dc_filter[i] = 0;
+    }
+    s->baud_phase = 0;
+#else
+    for (i = 0;  i < 2;  i++)
+    {
+        s->symbol_sync_low[i] = 0.0f;
+        s->symbol_sync_high[i] = 0.0f;
+        s->symbol_sync_dc_filter[i] = 0.0f;
+    }
+    s->baud_phase = 0.0f;
+#endif
+    s->baud_half = 0;
+    
+    s->total_baud_timing_correction = 0;
+
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(v17_rx_state_t *) v17_rx_init(v17_rx_state_t *s, int bit_rate, put_bit_func_t put_bit, void *user_data)
+{
+    switch (bit_rate)
+    {
+    case 14400:
+    case 12000:
+    case 9600:
+    case 7200:
+    case 4800:
+        /* 4800 is an extension of V.17, to provide full converage of the V.32bis modes */
+        break;
+    default:
+        return NULL;
+    }
+    if (s == NULL)
+    {
+        if ((s = (v17_rx_state_t *) malloc(sizeof(*s))) == NULL)
+            return NULL;
+    }
+    memset(s, 0, sizeof(*s));
+    span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
+    span_log_set_protocol(&s->logging, "V.17 RX");
+    s->put_bit = put_bit;
+    s->put_bit_user_data = user_data;
+    s->short_train = FALSE;
+    //s->scrambler_tap = 18 - 1;
+    v17_rx_signal_cutoff(s, -45.5f);
+    s->agc_scaling = 0.0017f/RX_PULSESHAPER_GAIN;
+    s->agc_scaling_save = 0.0f;
+    s->carrier_phase_rate_save = dds_phase_ratef(CARRIER_NOMINAL_FREQ);
+    v17_rx_restart(s, bit_rate, s->short_train);
+    return s;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) v17_rx_release(v17_rx_state_t *s)
+{
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(int) v17_rx_free(v17_rx_state_t *s)
+{
+    free(s);
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+SPAN_DECLARE(void) v17_rx_set_qam_report_handler(v17_rx_state_t *s, qam_report_handler_t handler, void *user_data)
+{
+    s->qam_report = handler;
+    s->qam_user_data = user_data;
+}
+/*- End of function --------------------------------------------------------*/
+/*- End of file ------------------------------------------------------------*/

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