Mercurial > hg > audiostuff
diff spandsp-0.0.6pre17/src/v29rx.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/v29rx.c Fri Jun 25 15:50:58 2010 +0200 @@ -0,0 +1,1227 @@ +#define IAXMODEM_STUFF +/* + * SpanDSP - a series of DSP components for telephony + * + * v29rx.c - ITU V.29 modem receive part + * + * Written by Steve Underwood <steveu@coppice.org> + * + * Copyright (C) 2003, 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: v29rx.c,v 1.167.4.5 2009/12/28 12:20:47 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/private/logging.h" +#include "spandsp/private/v29rx.h" + +#include "v29tx_constellation_maps.h" +#if defined(SPANDSP_USE_FIXED_POINT) +#include "v29rx_fixed_rrc.h" +#else +#include "v29rx_floating_rrc.h" +#endif + +/*! The nominal frequency of the carrier, in Hertz */ +#define CARRIER_NOMINAL_FREQ 1700.0f +/*! The nominal baud or symbol rate */ +#define BAUD_RATE 2400 +/*! The adaption rate coefficient for the equalizer */ +#define EQUALIZER_DELTA 0.21f + +#if defined(SPANDSP_USE_FIXED_POINT) +#define FP_FACTOR 4096 +#define FP_SHIFT_FACTOR 12 +#endif + +/* Segments of the training sequence */ +/*! The length of training segment 2, in symbols */ +#define V29_TRAINING_SEG_2_LEN 128 +/*! The length of training segment 3, in symbols */ +#define V29_TRAINING_SEG_3_LEN 384 +/*! The length of training segment 4, in symbols */ +#define V29_TRAINING_SEG_4_LEN 48 + +/*! The length of the equalizer buffer */ +#define V29_EQUALIZER_LEN (V29_EQUALIZER_PRE_LEN + 1 + V29_EQUALIZER_POST_LEN) + +enum +{ + TRAINING_STAGE_NORMAL_OPERATION = 0, + TRAINING_STAGE_SYMBOL_ACQUISITION, + TRAINING_STAGE_LOG_PHASE, + TRAINING_STAGE_WAIT_FOR_CDCD, + TRAINING_STAGE_TRAIN_ON_CDCD, + TRAINING_STAGE_TRAIN_ON_CDCD_AND_TEST, + TRAINING_STAGE_TEST_ONES, + TRAINING_STAGE_PARKED +}; + +static const uint8_t space_map_9600[20][20] = +{ + {13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 12, 11, 11, 11, 11, 11, 11}, + {13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 11, 11, 11, 11, 11, 11, 11}, + {13, 13, 13, 13, 13, 13, 13, 4, 4, 4, 4, 4, 4, 11, 11, 11, 11, 11, 11, 11}, + {13, 13, 13, 13, 13, 13, 13, 4, 4, 4, 4, 4, 4, 11, 11, 11, 11, 11, 11, 11}, + {13, 13, 13, 13, 13, 13, 13, 4, 4, 4, 4, 4, 4, 11, 11, 11, 11, 11, 11, 11}, + {13, 13, 13, 13, 13, 13, 13, 5, 4, 4, 4, 4, 3, 11, 11, 11, 11, 11, 11, 11}, + {14, 13, 13, 13, 13, 13, 5, 5, 5, 5, 3, 3, 3, 3, 11, 11, 11, 11, 11, 10}, + {14, 14, 6, 6, 6, 5, 5, 5, 5, 5, 3, 3, 3, 3, 3, 2, 2, 2, 10, 10}, + {14, 14, 6, 6, 6, 6, 5, 5, 5, 5, 3, 3, 3, 3, 2, 2, 2, 2, 10, 10}, + {14, 14, 6, 6, 6, 6, 5, 5, 5, 5, 3, 3, 3, 3, 2, 2, 2, 2, 10, 10}, + {14, 14, 6, 6, 6, 6, 7, 7, 7, 7, 1, 1, 1, 1, 2, 2, 2, 2, 10, 10}, + {14, 14, 6, 6, 6, 6, 7, 7, 7, 7, 1, 1, 1, 1, 2, 2, 2, 2, 10, 10}, + {14, 14, 6, 6, 6, 7, 7, 7, 7, 7, 1, 1, 1, 1, 1, 2, 2, 2, 10, 10}, + {14, 15, 15, 15, 15, 15, 7, 7, 7, 7, 1, 1, 1, 1, 9, 9, 9, 9, 9, 10}, + {15, 15, 15, 15, 15, 15, 15, 7, 0, 0, 0, 0, 1, 9, 9, 9, 9, 9, 9, 9}, + {15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9}, + {15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9}, + {15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9}, + {15, 15, 15, 15, 15, 15, 15, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9}, + {15, 15, 15, 15, 15, 15, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9} +}; + +/* 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.382683432f +#define COS_LOW_BAND_EDGE 0.923879533f +#define SIN_HIGH_BAND_EDGE 0.760405966f +#define COS_HIGH_BAND_EDGE -0.649448048f +#define ALPHA 0.99f + +#if defined(SPANDSP_USE_FIXED_POINT) +#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 + +SPAN_DECLARE(float) v29_rx_carrier_frequency(v29_rx_state_t *s) +{ + return dds_frequencyf(s->carrier_phase_rate); +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(float) v29_rx_symbol_timing_correction(v29_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) v29_rx_signal_power(v29_rx_state_t *s) +{ + return power_meter_current_dbm0(&s->power) + 3.98f; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(void) v29_rx_signal_cutoff(v29_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(v29_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_POINT) +SPAN_DECLARE(int) v29_rx_equalizer_state(v29_rx_state_t *s, complexi16_t **coeffs) +#else +SPAN_DECLARE(int) v29_rx_equalizer_state(v29_rx_state_t *s, complexf_t **coeffs) +#endif +{ + *coeffs = s->eq_coeff; + return V29_EQUALIZER_LEN; +} +/*- End of function --------------------------------------------------------*/ + +static void equalizer_save(v29_rx_state_t *s) +{ +#if defined(SPANDSP_USE_FIXED_POINT) + cvec_copyi16(s->eq_coeff_save, s->eq_coeff, V29_EQUALIZER_LEN); +#else + cvec_copyf(s->eq_coeff_save, s->eq_coeff, V29_EQUALIZER_LEN); +#endif +} +/*- End of function --------------------------------------------------------*/ + +static void equalizer_restore(v29_rx_state_t *s) +{ +#if defined(SPANDSP_USE_FIXED_POINT) + cvec_copyi16(s->eq_coeff, s->eq_coeff_save, V29_EQUALIZER_LEN); + cvec_zeroi16(s->eq_buf, V29_EQUALIZER_LEN); + s->eq_delta = 32768.0f*EQUALIZER_DELTA/V29_EQUALIZER_LEN; +#else + cvec_copyf(s->eq_coeff, s->eq_coeff_save, V29_EQUALIZER_LEN); + cvec_zerof(s->eq_buf, V29_EQUALIZER_LEN); + s->eq_delta = EQUALIZER_DELTA/V29_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(v29_rx_state_t *s) +{ + /* Start with an equalizer based on everything being perfect */ +#if defined(SPANDSP_USE_FIXED_POINT) + cvec_zeroi16(s->eq_coeff, V29_EQUALIZER_LEN); + s->eq_coeff[V29_EQUALIZER_POST_LEN] = complex_seti16(3*FP_FACTOR, 0*FP_FACTOR); + cvec_zeroi16(s->eq_buf, V29_EQUALIZER_LEN); + s->eq_delta = 32768.0f*EQUALIZER_DELTA/V29_EQUALIZER_LEN; +#else + cvec_zerof(s->eq_coeff, V29_EQUALIZER_LEN); + s->eq_coeff[V29_EQUALIZER_POST_LEN] = complex_setf(3.0f, 0.0f); + cvec_zerof(s->eq_buf, V29_EQUALIZER_LEN); + s->eq_delta = EQUALIZER_DELTA/V29_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_POINT) +static __inline__ complexi16_t complex_mul_q4_12(const complexi16_t *x, const complexi16_t *y) +{ + complexi16_t z; + + z.re = ((int32_t) x->re*(int32_t) y->re - (int32_t) x->im*(int32_t) y->im) >> FP_SHIFT_FACTOR; + z.im = ((int32_t) x->re*(int32_t) y->im + (int32_t) x->im*(int32_t) y->re) >> FP_SHIFT_FACTOR; + return z; +} +/*- End of function --------------------------------------------------------*/ +#endif + +#if defined(SPANDSP_USE_FIXED_POINT) +static __inline__ complexi16_t equalizer_get(v29_rx_state_t *s) +#else +static __inline__ complexf_t equalizer_get(v29_rx_state_t *s) +#endif +{ +#if defined(SPANDSP_USE_FIXED_POINT) + complexi32_t zz; + complexi16_t z; + + /* Get the next equalized value. */ + zz = cvec_circular_dot_prodi16(s->eq_buf, s->eq_coeff, V29_EQUALIZER_LEN, s->eq_step); + z.re = zz.re >> FP_SHIFT_FACTOR; + z.im = zz.im >> FP_SHIFT_FACTOR; + return z; +#else + /* Get the next equalized value. */ + return cvec_circular_dot_prodf(s->eq_buf, s->eq_coeff, V29_EQUALIZER_LEN, s->eq_step); +#endif +} +/*- End of function --------------------------------------------------------*/ + +#if defined(SPANDSP_USE_FIXED_POINT) +static void tune_equalizer(v29_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; + 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, V29_EQUALIZER_LEN, s->eq_step, &err); +} +#else +static void tune_equalizer(v29_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); + err.re *= s->eq_delta; + err.im *= s->eq_delta; + cvec_circular_lmsf(s->eq_buf, s->eq_coeff, V29_EQUALIZER_LEN, s->eq_step, &err); +} +#endif +/*- End of function --------------------------------------------------------*/ + +static int scrambled_training_bit(v29_rx_state_t *s) +{ + int bit; + + /* Segment 3 of the training sequence - the scrambled CDCD part. */ + /* Apply the 1 + x^-6 + x^-7 scrambler */ + bit = s->training_scramble_reg & 1; + s->training_scramble_reg >>= 1; + if (bit ^ (s->training_scramble_reg & 1)) + s->training_scramble_reg |= 0x40; + return bit; +} +/*- End of function --------------------------------------------------------*/ + +#if defined(SPANDSP_USE_FIXED_POINT) +static __inline__ int find_quadrant(const complexi16_t *z) +#else +static __inline__ int find_quadrant(const complexf_t *z) +#endif +{ + int b1; + int b2; + + /* Split the space along the two diagonals. */ + b1 = (z->im > z->re); + b2 = (z->im < -z->re); + return (b2 << 1) | (b1 ^ b2); +} +/*- End of function --------------------------------------------------------*/ + +#if defined(SPANDSP_USE_FIXED_POINT) +static __inline__ void track_carrier(v29_rx_state_t *s, const complexi16_t *z, const complexi16_t *target) +#else +static __inline__ void track_carrier(v29_rx_state_t *s, const complexf_t *z, const complexf_t *target) +#endif +{ +#if defined(SPANDSP_USE_FIXED_POINT) + int32_t error; +#else + float error; +#endif + + /* The initial coarse carrier frequency and phase estimation should have + got us in the right ballpark. Now we need to fine tune fairly quickly, + to get the receovered carrier more precisely on target. Then we need to + fine tune in a more damped way to keep us on target. The goal is to have + things running really well by the time the training is complete. + We assume the frequency of the oscillators at the two ends drift only + very slowly. The PSTN has rather limited doppler problems. :-) Any + remaining FDM in the network should also drift slowly. */ + /* 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. This isn't all bad, + as the angular error for the larger amplitude constellation points is probably + a more reliable indicator, and we are weighting it as such. */ + error = z->im*target->re - z->re*target->im; + + /* Use a proportional-integral approach to tracking the carrier. The PI + parameters are coarser at first, until we get precisely on target. Then, + the filter will be damped more to keep us on target. */ +#if defined(SPANDSP_USE_FIXED_POINT) + s->carrier_phase_rate += ((s->carrier_track_i*error) >> FP_SHIFT_FACTOR); + s->carrier_phase += ((s->carrier_track_p*error) >> FP_SHIFT_FACTOR); +#else + 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)); +#endif +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void put_bit(v29_rx_state_t *s, int bit) +{ + int out_bit; + + bit &= 1; + + /* Descramble the bit */ + out_bit = (bit ^ (s->scramble_reg >> 17) ^ (s->scramble_reg >> 22)) & 1; + s->scramble_reg = (s->scramble_reg << 1) | 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) + { + s->put_bit(s->put_bit_user_data, out_bit); + } + else + { + /* 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. */ + } +} +/*- End of function --------------------------------------------------------*/ + +#if defined(SPANDSP_USE_FIXED_POINT) +static void decode_baud(v29_rx_state_t *s, complexi16_t *z) +#else +static void decode_baud(v29_rx_state_t *s, complexf_t *z) +#endif +{ + static const uint8_t phase_steps_9600[8] = + { + 4, 0, 2, 6, 7, 3, 1, 5 + }; + static const uint8_t phase_steps_4800[4] = + { + 0, 2, 3, 1 + }; + int nearest; + int raw_bits; + int i; + int re; + int im; + + if (s->bit_rate == 4800) + { + /* 4800 is a special case. */ + nearest = find_quadrant(z) << 1; + raw_bits = phase_steps_4800[((nearest - s->constellation_state) >> 1) & 3]; + put_bit(s, raw_bits); + put_bit(s, raw_bits >> 1); + } + else + { + /* 9600 and 7200 are quite similar. */ +#if defined(SPANDSP_USE_FIXED_POINT) + re = (z->re + 5*FP_FACTOR) >> (FP_SHIFT_FACTOR - 1); + im = (z->im + 5*FP_FACTOR) >> (FP_SHIFT_FACTOR - 1); +#else + re = (int) ((z->re + 5.0f)*2.0f); + im = (int) ((z->im + 5.0f)*2.0f); +#endif + if (re > 19) + re = 19; + else if (re < 0) + re = 0; + if (im > 19) + im = 19; + else if (im < 0) + im = 0; + nearest = space_map_9600[re][im]; + if (s->bit_rate == 9600) + { + /* Send out the top (amplitude) bit. */ + put_bit(s, nearest >> 3); + } + else + { + /* We can reuse the space map for 9600, but drop the top bit. */ + nearest &= 7; + } + raw_bits = phase_steps_9600[(nearest - s->constellation_state) & 7]; + for (i = 0; i < 3; i++) + { + put_bit(s, raw_bits); + raw_bits >>= 1; + } + } + + track_carrier(s, z, &v29_9600_constellation[nearest]); + if (--s->eq_skip <= 0) + { + /* Once we are in the data the equalization should not need updating. + However, the line characteristics may slowly drift. We, therefore, + tune up on the occassional sample, keeping the compute down. */ + s->eq_skip = 10; + tune_equalizer(s, z, &v29_9600_constellation[nearest]); + } + s->constellation_state = nearest; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void symbol_sync(v29_rx_state_t *s) +{ + int i; +#if defined(SPANDSP_USE_FIXED_POINT) + 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_POINT) + /* 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) > 30*FP_FACTOR) + { + if (s->baud_phase > 0) + i = (s->baud_phase > 1000*FP_FACTOR) ? 5 : 1; + else + i = (s->baud_phase < -1000*FP_FACTOR) ? -5 : -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) > 30.0f) + { + if (s->baud_phase > 0.0f) + i = (s->baud_phase > 1000.0f) ? 5 : 1; + else + i = (s->baud_phase < -1000.0f) ? -5 : -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; + } +#endif +} +/*- End of function --------------------------------------------------------*/ + +#if defined(SPANDSP_USE_FIXED_POINT) +static void process_half_baud(v29_rx_state_t *s, complexi16_t *sample) +#else +static void process_half_baud(v29_rx_state_t *s, complexf_t *sample) +#endif +{ + static const int cdcd_pos[6] = + { + 0, 11, + 0, 3, + 0, 2 + }; + complexf_t zz; +#if defined(SPANDSP_USE_FIXED_POINT) + complexf_t z1; + complexi16_t z; + const complexi16_t *target; + static const complexi16_t zero = {0, 0}; +#else + complexf_t z; + const complexf_t *target; + static const complexf_t zero = {0.0f, 0.0f}; +#endif + float p; + int bit; + int i; + int j; + int32_t angle; + int32_t ang; + + /* 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 >= V29_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); + + switch (s->training_stage) + { + case TRAINING_STAGE_NORMAL_OPERATION: + /* Normal operation. */ + decode_baud(s, &z); + target = &v29_9600_constellation[s->constellation_state]; + break; + case TRAINING_STAGE_SYMBOL_ACQUISITION: + /* Allow time for symbol synchronisation to settle the symbol timing. */ + target = &zero; + if (++s->training_count >= 60) + { + /* Record the current phase angle */ + s->training_stage = TRAINING_STAGE_LOG_PHASE; + s->angles[0] = + s->start_angles[0] = arctan2(z.im, z.re); +#if defined(SPANDSP_USE_FIXED_POINT) + if (s->agc_scaling_save == 0) + s->agc_scaling_save = s->agc_scaling; +#else + if (s->agc_scaling_save == 0.0f) + s->agc_scaling_save = s->agc_scaling; +#endif + } + break; + case TRAINING_STAGE_LOG_PHASE: + /* Record the current alternate phase angle */ + target = &zero; + s->angles[1] = + s->start_angles[1] = arctan2(z.im, z.re); + s->training_count = 1; + s->training_stage = TRAINING_STAGE_WAIT_FOR_CDCD; + break; + case TRAINING_STAGE_WAIT_FOR_CDCD: + 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 CDCD segment */ + ang = angle - s->angles[(s->training_count - 1) & 0xF]; + s->angles[(s->training_count + 1) & 0xF] = angle; + if ((ang > 0x20000000 || ang < -0x20000000) && s->training_count >= 13) + { + /* 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. We need to scale here, or at the maximum specified + frequency deviation we could overflow, and get a silly answer. */ + /* 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) + { + 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, "Coarse carrier frequency %7.2f\n", dds_frequencyf(s->carrier_phase_rate)); + /* 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 */ +#if defined(SPANDSP_USE_FIXED_POINT) + s->agc_scaling_save = 0; +#else + s->agc_scaling_save = 0.0f; +#endif + 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. */ + p = angle*2.0f*3.14159f/(65536.0f*65536.0f); +#if defined(SPANDSP_USE_FIXED_POINT) + zz = complex_setf(cosf(p), -sinf(p)); + for (i = 0; i < V29_EQUALIZER_LEN; i++) + { + z1 = complex_setf(s->eq_buf[i].re, s->eq_buf[i].im); + z1 = complex_mulf(&z1, &zz); + s->eq_buf[i].re = z1.re; + s->eq_buf[i].im = z1.im; + } +#else + zz = complex_setf(cosf(p), -sinf(p)); + for (i = 0; i < V29_EQUALIZER_LEN; i++) + s->eq_buf[i] = complex_mulf(&s->eq_buf[i], &zz); +#endif + s->carrier_phase += angle; + /* We have just seen the first bit of the scrambled sequence, so skip it. */ + bit = scrambled_training_bit(s); + s->constellation_state = cdcd_pos[s->training_cd + bit]; + target = &v29_9600_constellation[s->constellation_state]; + s->training_count = 1; + s->training_stage = TRAINING_STAGE_TRAIN_ON_CDCD; + report_status_change(s, SIG_STATUS_TRAINING_IN_PROGRESS); + break; + } + if (++s->training_count > V29_TRAINING_SEG_2_LEN) + { + /* This is bogus. There are not this many bauds in this section + of a real training sequence. */ + span_log(&s->logging, SPAN_LOG_FLOW, "Training failed (sequence failed)\n"); + /* Park this modem */ +#if defined(SPANDSP_USE_FIXED_POINT) + s->agc_scaling_save = 0; +#else + s->agc_scaling_save = 0.0f; +#endif + s->training_stage = TRAINING_STAGE_PARKED; + report_status_change(s, SIG_STATUS_TRAINING_FAILED); + } + break; + case TRAINING_STAGE_TRAIN_ON_CDCD: + /* Train on the scrambled CDCD section. */ + bit = scrambled_training_bit(s); + //span_log(&s->logging, SPAN_LOG_FLOW, "%5d %15.5f, %15.5f %15.5f, %15.5f\n", s->training_count, z.re, z.im, v29_9600_constellation[cdcd_pos[s->training_cd + bit]].re, v29_9600_constellation[cdcd_pos[s->training_cd + bit]].im); + s->constellation_state = cdcd_pos[s->training_cd + bit]; + target = &v29_9600_constellation[s->constellation_state]; + track_carrier(s, &z, target); + tune_equalizer(s, &z, target); + if (++s->training_count >= V29_TRAINING_SEG_3_LEN - 48) + { + s->training_stage = TRAINING_STAGE_TRAIN_ON_CDCD_AND_TEST; + s->training_error = 0.0f; +#if defined(SPANDSP_USE_FIXED_POINT) + s->carrier_track_i = 200; + s->carrier_track_p = 1000000; +#else + s->carrier_track_i = 200.0f; + s->carrier_track_p = 1000000.0f; +#endif + } + break; + case TRAINING_STAGE_TRAIN_ON_CDCD_AND_TEST: + /* Continue training on the scrambled CDCD section, but measure the quality of training too. */ + bit = scrambled_training_bit(s); + //span_log(&s->logging, SPAN_LOG_FLOW, "%5d %15.5f, %15.5f %15.5f, %15.5f\n", s->training_count, z.re, z.im, v29_9600_constellation[cdcd_pos[s->training_cd + bit]].re, v29_9600_constellation[cdcd_pos[s->training_cd + bit]].im); + s->constellation_state = cdcd_pos[s->training_cd + bit]; + target = &v29_9600_constellation[s->constellation_state]; + track_carrier(s, &z, target); + tune_equalizer(s, &z, target); + /* Measure the training error */ +#if defined(SPANDSP_USE_FIXED_POINT) + z1.re = z.re/(float) FP_FACTOR; + z1.im = z.im/(float) FP_FACTOR; + zz.re = target->re; + zz.im = target->im; + zz = complex_subf(&z1, &zz); + s->training_error += powerf(&zz); +#else + zz = complex_subf(&z, target); + s->training_error += powerf(&zz); +#endif + if (++s->training_count >= V29_TRAINING_SEG_3_LEN) + { + span_log(&s->logging, SPAN_LOG_FLOW, "Constellation mismatch %f\n", s->training_error); + if (s->training_error < 48.0f*2.0f) + { + s->training_count = 0; + s->training_error = 0.0f; + s->constellation_state = 0; + s->training_stage = TRAINING_STAGE_TEST_ONES; + } + else + { + span_log(&s->logging, SPAN_LOG_FLOW, "Training failed (convergence failed)\n"); + /* Park this modem */ +#if defined(SPANDSP_USE_FIXED_POINT) + s->agc_scaling_save = 0; +#else + s->agc_scaling_save = 0.0f; +#endif + s->training_stage = TRAINING_STAGE_PARKED; + report_status_change(s, SIG_STATUS_TRAINING_FAILED); + } + } + 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 (192 bits at 9600bps) long. */ + //span_log(&s->logging, SPAN_LOG_FLOW, "%5d %15.5f, %15.5f\n", s->training_count, z.re, z.im); + decode_baud(s, &z); + target = &v29_9600_constellation[s->constellation_state]; + /* Measure the training error */ +#if defined(SPANDSP_USE_FIXED_POINT) + z1.re = z.re/(float) FP_FACTOR; + z1.im = z.im/(float) FP_FACTOR; + zz.re = target->re; + zz.im = target->im; + zz = complex_subf(&z1, &zz); + s->training_error += powerf(&zz); +#else + zz = complex_subf(&z, target); + s->training_error += powerf(&zz); +#endif + if (++s->training_count >= V29_TRAINING_SEG_4_LEN) + { + if (s->training_error < 48.0f) + { + /* 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; + s->training_stage = TRAINING_STAGE_NORMAL_OPERATION; + equalizer_save(s); + s->carrier_phase_rate_save = s->carrier_phase_rate; + s->agc_scaling_save = s->agc_scaling; + } + else + { + /* Training has failed */ + span_log(&s->logging, SPAN_LOG_FLOW, "Training failed (constellation mismatch %f)\n", s->training_error); + /* Park this modem */ +#if defined(SPANDSP_USE_FIXED_POINT) + s->agc_scaling_save = 0; +#else + s->agc_scaling_save = 0.0f; +#endif + 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) + { +#if defined(SPANDSP_USE_FIXED_POINT) + z1.re = z.re/(float) FP_FACTOR; + z1.im = z.im/(float) FP_FACTOR; + zz.re = target->re; + zz.im = target->im; + s->qam_report(s->qam_user_data, &z1, &zz, s->constellation_state); +#else + s->qam_report(s->qam_user_data, &z, target, s->constellation_state); +#endif + } +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int signal_detect(v29_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. */ + v29_rx_restart(s, s->bit_rate, FALSE); + 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) v29_rx(v29_rx_state_t *s, const int16_t amp[], int len) +{ + int i; + int step; +#if defined(SPANDSP_USE_FIXED_POINT) + complexi16_t z; + complexi16_t zz; + complexi16_t sample; + int32_t v; +#else + complexf_t z; + complexf_t zz; + complexf_t sample; + 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 >= V29_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) + v = vec_circular_dot_prodi16(s->rrc_filter, rx_pulseshaper_re[step], V29_RX_FILTER_STEPS, s->rrc_filter_step); + sample.re = (v*s->agc_scaling) >> 15; +#else + v = vec_circular_dot_prodf(s->rrc_filter, rx_pulseshaper_re[step], V29_RX_FILTER_STEPS, s->rrc_filter_step); + sample.re = v*s->agc_scaling; +#endif + + /* Symbol timing synchronisation band edge filters */ +#if defined(SPANDSP_USE_FIXED_POINT) + /* Low Nyquist band edge filter */ + v = ((s->symbol_sync_low[0]*SYNC_LOW_BAND_EDGE_COEFF_0) >> FP_SHIFT_FACTOR) + ((s->symbol_sync_low[1]*SYNC_LOW_BAND_EDGE_COEFF_1) >> FP_SHIFT_FACTOR) + 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) >> FP_SHIFT_FACTOR) + ((s->symbol_sync_high[1]*SYNC_HIGH_BAND_EDGE_COEFF_1) >> FP_SHIFT_FACTOR) + sample.re; + s->symbol_sync_high[1] = s->symbol_sync_high[0]; + s->symbol_sync_high[0] = v; +#else + /* 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; +#endif + /* Put things into the equalization buffer at T/2 rate. The symbol synchronisation + 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 defined(SPANDSP_USE_FIXED_POINT) + if (s->agc_scaling_save == 0) + s->agc_scaling = (float) FP_FACTOR*32768.0f*(1.0f/RX_PULSESHAPER_GAIN)*5.0f*0.25f/sqrtf(power); +#else + if (s->agc_scaling_save == 0.0f) + s->agc_scaling = (1.0f/RX_PULSESHAPER_GAIN)*5.0f*0.25f/sqrtf(power); +#endif + /* 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) + v = vec_circular_dot_prodi16(s->rrc_filter, rx_pulseshaper_im[step], V29_RX_FILTER_STEPS, s->rrc_filter_step); + sample.im = (v*s->agc_scaling) >> 15; + z = dds_lookup_complexi16(s->carrier_phase); + zz.re = ((int32_t) sample.re*(int32_t) z.re - (int32_t) sample.im*(int32_t) z.im) >> 15; + zz.im = ((int32_t) -sample.re*(int32_t) z.im - (int32_t) sample.im*(int32_t) z.re) >> 15; +#else + v = vec_circular_dot_prodf(s->rrc_filter, rx_pulseshaper_im[step], V29_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) v29_rx_fillin(v29_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) v29_rx_set_put_bit(v29_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) v29_rx_set_modem_status_handler(v29_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 *) v29_rx_get_logging_state(v29_rx_state_t *s) +{ + return &s->logging; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) v29_rx_restart(v29_rx_state_t *s, int bit_rate, int old_train) +{ + int i; + + switch (bit_rate) + { + case 9600: + s->training_cd = 0; + break; + case 7200: + s->training_cd = 2; + break; + case 4800: + s->training_cd = 4; + 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->scramble_reg = 0; + s->training_scramble_reg = 0x2A; + s->training_stage = TRAINING_STAGE_SYMBOL_ACQUISITION; + s->training_count = 0; + s->signal_present = 0; +#if defined(IAXMODEM_STUFF) + s->high_sample = 0; + s->low_samples = 0; + s->carrier_drop_pending = FALSE; +#endif + s->old_train = old_train; + + s->carrier_phase = 0; + + power_meter_init(&(s->power), 4); + + s->constellation_state = 0; + + if (s->old_train) + { + s->carrier_phase_rate = s->carrier_phase_rate_save; + s->agc_scaling = s->agc_scaling_save; + equalizer_restore(s); + } + else + { + s->carrier_phase_rate = dds_phase_ratef(CARRIER_NOMINAL_FREQ); +#if defined(SPANDSP_USE_FIXED_POINT) + s->agc_scaling_save = 0; + s->agc_scaling = (float) FP_FACTOR*32768.0f*0.0017f/RX_PULSESHAPER_GAIN; +#else + s->agc_scaling_save = 0.0f; + s->agc_scaling = 0.0017f/RX_PULSESHAPER_GAIN; +#endif + equalizer_reset(s); + } +#if defined(SPANDSP_USE_FIXED_POINT) + s->carrier_track_i = 8000; + s->carrier_track_p = 8000000; +#else + s->carrier_track_i = 8000.0f; + s->carrier_track_p = 8000000.0f; +#endif + s->last_sample = 0; + s->eq_skip = 0; + + /* Initialise the working data for symbol timing synchronisation */ +#if defined(SPANDSP_USE_FIXED_POINT) + 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(v29_rx_state_t *) v29_rx_init(v29_rx_state_t *s, int bit_rate, put_bit_func_t put_bit, void *user_data) +{ + switch (bit_rate) + { + case 9600: + case 7200: + case 4800: + break; + default: + return NULL; + } + if (s == NULL) + { + if ((s = (v29_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.29 RX"); + s->put_bit = put_bit; + s->put_bit_user_data = user_data; + /* The V.29 spec says the thresholds should be -31dBm and -26dBm, but that makes little + sense. V.17 uses -48dBm and -43dBm, and there seems no good reason to cut off at a + higher level (though at 9600bps and 7200bps, TCM should put V.17 sensitivity several + dB ahead of V.29). */ + /* The thresholds should be on at -26dBm0 and off at -31dBm0 */ + v29_rx_signal_cutoff(s, -28.5f); + + v29_rx_restart(s, bit_rate, FALSE); + return s; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) v29_rx_release(v29_rx_state_t *s) +{ + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(int) v29_rx_free(v29_rx_state_t *s) +{ + free(s); + return 0; +} +/*- End of function --------------------------------------------------------*/ + +SPAN_DECLARE(void) v29_rx_set_qam_report_handler(v29_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 ------------------------------------------------------------*/