Mercurial > hg > audiostuff
diff spandsp-0.0.3/spandsp-0.0.3/src/v27ter_rx.c @ 5:f762bf195c4b
import spandsp-0.0.3
author | Peter Meerwald <pmeerw@cosy.sbg.ac.at> |
---|---|
date | Fri, 25 Jun 2010 16:00:21 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/spandsp-0.0.3/spandsp-0.0.3/src/v27ter_rx.c Fri Jun 25 16:00:21 2010 +0200 @@ -0,0 +1,1443 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * v27ter_rx.c - ITU V.27ter modem receive part + * + * Written by Steve Underwood <steveu@coppice.org> + * + * Copyright (C) 2003 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: v27ter_rx.c,v 1.75 2006/11/28 16:59:57 steveu Exp $ + */ + +/*! \file */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <inttypes.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#if defined(HAVE_TGMATH_H) +#include <tgmath.h> +#endif +#if defined(HAVE_MATH_H) +#include <math.h> +#endif + +#include "spandsp/telephony.h" +#include "spandsp/logging.h" +#include "spandsp/complex.h" +#include "spandsp/vector_float.h" +#include "spandsp/complex_vector_float.h" +#include "spandsp/async.h" +#include "spandsp/power_meter.h" +#include "spandsp/arctan2.h" +#include "spandsp/dds.h" +#include "spandsp/complex_filters.h" + +#include "spandsp/v29rx.h" +#include "spandsp/v27ter_rx.h" + +/* V.27ter is a DPSK modem, but this code treats it like QAM. It nails down the + signal to a static constellation, even though dealing with differences is all + that is necessary. */ + +#define CARRIER_NOMINAL_FREQ 1800.0f +#define EQUALIZER_DELTA 0.25f + +/* Segments of the training sequence */ +/* V.27ter defines a long and a short sequence. FAX doesn't use the + short sequence, so it is not implemented here. */ +#define V27TER_TRAINING_SEG_3_LEN 50 +#define V27TER_TRAINING_SEG_5_LEN 1074 +#define V27TER_TRAINING_SEG_6_LEN 8 + +enum +{ + TRAINING_STAGE_NORMAL_OPERATION = 0, + TRAINING_STAGE_SYMBOL_ACQUISITION, + TRAINING_STAGE_LOG_PHASE, + TRAINING_STAGE_WAIT_FOR_HOP, + TRAINING_STAGE_TRAIN_ON_ABAB, + TRAINING_STAGE_TEST_ONES, + TRAINING_STAGE_PARKED +}; + +static const complexf_t v27ter_constellation[8] = +{ + { 1.414f, 0.0f}, /* 0deg */ + { 1.0f, 1.0f}, /* 45deg */ + { 0.0f, 1.414f}, /* 90deg */ + {-1.0f, 1.0f}, /* 135deg */ + {-1.414f, 0.0f}, /* 180deg */ + {-1.0f, -1.0f}, /* 225deg */ + { 0.0f, -1.414f}, /* 270deg */ + { 1.0f, -1.0f} /* 315deg */ +}; + +/* Raised root cosine pulse shaping filter set; beta 0.5; sample rate 8000; 8 phase steps; + baud rate 1600; shifted to centre at 1800Hz; complex. */ +#define PULSESHAPER_4800_GAIN (2.4975f*2.0f) +#define PULSESHAPER_4800_COEFF_SETS 8 +static const complexf_t pulseshaper_4800[PULSESHAPER_4800_COEFF_SETS][V27TER_RX_4800_FILTER_STEPS] = +{ + { + {-0.0050334423f, -0.0025646669f}, /* Filter 0 */ + { 0.0001996320f, -0.0006144041f}, + {-0.0064914716f, -0.0010281481f}, + {-0.0000000000f, 0.0057152766f}, + {-0.0060638961f, 0.0009604268f}, + { 0.0046534477f, 0.0143218395f}, + {-0.0027026909f, 0.0013770898f}, + { 0.0114324470f, 0.0157354133f}, + { 0.0161335660f, -0.0161335660f}, + { 0.0216170550f, 0.0157057098f}, + { 0.0523957134f, -0.1028323775f}, + { 0.1009107956f, 0.0327879051f}, + { 0.0626681574f, -0.3956711736f}, + { 1.0309650898f, 0.0000000000f}, + { 0.1612784723f, 1.0182721987f}, + {-0.3809963452f, 0.1237932168f}, + { 0.0481701579f, 0.0945392579f}, + {-0.0933698449f, 0.0678371631f}, + { 0.0188939989f, 0.0188939989f}, + {-0.0134110893f, 0.0184587808f}, + { 0.0173301130f, 0.0088301336f}, + { 0.0009373415f, -0.0028848406f}, + { 0.0148734735f, 0.0023557268f}, + {-0.0000000000f, -0.0061394833f}, + { 0.0056449120f, -0.0008940662f}, + {-0.0020309798f, -0.0062507130f}, + {-0.0005756104f, 0.0002932882f}, + }, + { + {-0.0018682578f, -0.0009519249f}, /* Filter 1 */ + {-0.0002684621f, 0.0008262413f}, + {-0.0059141931f, -0.0009367162f}, + {-0.0000000000f, 0.0073941285f}, + {-0.0037772132f, 0.0005982518f}, + { 0.0050394423f, 0.0155098087f}, + { 0.0010806327f, -0.0005506098f}, + { 0.0105277637f, 0.0144902237f}, + { 0.0209691082f, -0.0209691082f}, + { 0.0125153543f, 0.0090929371f}, + { 0.0603186345f, -0.1183819857f}, + { 0.0675630592f, 0.0219525687f}, + { 0.0765237582f, -0.4831519944f}, + { 1.0763458014f, 0.0000000000f}, + { 0.1524445751f, 0.9624971666f}, + {-0.2992580667f, 0.0972348401f}, + { 0.0600222537f, 0.1178003057f}, + {-0.0774892752f, 0.0562992540f}, + { 0.0247376160f, 0.0247376159f}, + {-0.0090916622f, 0.0125135995f}, + { 0.0175076452f, 0.0089205908f}, + { 0.0021568809f, -0.0066381970f}, + { 0.0129897446f, 0.0020573734f}, + {-0.0000000000f, -0.0079766726f}, + { 0.0037729191f, -0.0005975717f}, + {-0.0020837980f, -0.0064132707f}, + {-0.0018682578f, 0.0009519249f}, + }, + { + {-0.0030355143f, -0.0015466718f}, /* Filter 2 */ + {-0.0007306011f, 0.0022485590f}, + {-0.0049435003f, -0.0007829735f}, + {-0.0000000000f, 0.0087472824f}, + {-0.0011144870f, 0.0001765174f}, + { 0.0051901643f, 0.0159736834f}, + { 0.0049297142f, -0.0025118148f}, + { 0.0088213528f, 0.0121415505f}, + { 0.0251126307f, -0.0251126307f}, + { 0.0011182680f, 0.0008124692f}, + { 0.0667555589f, -0.1310151612f}, + { 0.0256033627f, 0.0083190368f}, + { 0.0905183226f, -0.5715101964f}, + { 1.1095595360f, 0.0000000000f}, + { 0.1420835849f, 0.8970804494f}, + {-0.2215345589f, 0.0719809416f}, + { 0.0679608493f, 0.1333806768f}, + {-0.0606982839f, 0.0440998847f}, + { 0.0284660210f, 0.0284660210f}, + {-0.0047348689f, 0.0065169880f}, + { 0.0165731197f, 0.0084444263f}, + { 0.0032233168f, -0.0099203492f}, + { 0.0105861265f, 0.0016766777f}, + {-0.0000000000f, -0.0092685623f}, + { 0.0018009090f, -0.0002852360f}, + {-0.0020112222f, -0.0061899056f}, + {-0.0030355143f, 0.0015466718f}, + }, + { + {-0.0040182937f, -0.0020474229f}, /* Filter 3 */ + {-0.0011603659f, 0.0035712391f}, + {-0.0036173562f, -0.0005729329f}, + {-0.0000000000f, 0.0096778115f}, + { 0.0018022529f, -0.0002854488f}, + { 0.0050847711f, 0.0156493164f}, + { 0.0086257291f, -0.0043950285f}, + { 0.0063429899f, 0.0087303766f}, + { 0.0282322904f, -0.0282322904f}, + {-0.0123306868f, -0.0089587683f}, + { 0.0712060603f, -0.1397497620f}, + {-0.0248170325f, -0.0080635427f}, + { 0.1043647251f, -0.6589329411f}, + { 1.1298123598f, 0.0000000000f}, + { 0.1304361227f, 0.8235412673f}, + {-0.1491678531f, 0.0484675735f}, + { 0.0722366382f, 0.1417723850f}, + {-0.0437871917f, 0.0318132570f}, + { 0.0301678844f, 0.0301678844f}, + {-0.0005794433f, 0.0007975353f}, + { 0.0146599874f, 0.0074696367f}, + { 0.0040878789f, -0.0125811975f}, + { 0.0078126085f, 0.0012373956f}, + {-0.0000000000f, -0.0099797659f}, + {-0.0001576582f, 0.0000249706f}, + {-0.0018223262f, -0.0056085432f}, + {-0.0040182937f, 0.0020474229f}, + }, + { + {-0.0047695783f, -0.0024302215f}, /* Filter 4 */ + {-0.0015320920f, 0.0047152944f}, + {-0.0019955989f, -0.0003160718f}, + {-0.0000000000f, 0.0101070339f}, + { 0.0048302421f, -0.0007650352f}, + { 0.0047152968f, 0.0145121913f}, + { 0.0119428503f, -0.0060851862f}, + { 0.0031686377f, 0.0043612557f}, + { 0.0300119095f, -0.0300119094f}, + {-0.0274628457f, -0.0199529254f}, + { 0.0731841827f, -0.1436320457f}, + {-0.0832936387f, -0.0270637438f}, + { 0.1177684882f, -0.7435609707f}, + { 1.1366178989f, 0.0000000000f}, + { 0.1177684882f, 0.7435609707f}, + {-0.0832936387f, 0.0270637438f}, + { 0.0731841827f, 0.1436320457f}, + {-0.0274628457f, 0.0199529254f}, + { 0.0300119095f, 0.0300119094f}, + { 0.0031686377f, -0.0043612557f}, + { 0.0119428503f, 0.0060851862f}, + { 0.0047152968f, -0.0145121913f}, + { 0.0048302421f, 0.0007650352f}, + {-0.0000000000f, -0.0101070339f}, + {-0.0019955989f, 0.0003160718f}, + {-0.0015320920f, -0.0047152944f}, + {-0.0047695783f, 0.0024302215f}, + }, + { + {-0.0052564711f, -0.0026783058f}, /* Filter 5 */ + {-0.0018223262f, 0.0056085432f}, + {-0.0001576582f, -0.0000249706f}, + {-0.0000000000f, 0.0099797659f}, + { 0.0078126085f, -0.0012373956f}, + { 0.0040878789f, 0.0125811975f}, + { 0.0146599874f, -0.0074696367f}, + {-0.0005794433f, -0.0007975353f}, + { 0.0301678844f, -0.0301678844f}, + {-0.0437871917f, -0.0318132570f}, + { 0.0722366382f, -0.1417723850f}, + {-0.1491678531f, -0.0484675735f}, + { 0.1304361227f, -0.8235412673f}, + { 1.1298123598f, 0.0000000000f}, + { 0.1043647251f, 0.6589329411f}, + {-0.0248170325f, 0.0080635427f}, + { 0.0712060603f, 0.1397497620f}, + {-0.0123306868f, 0.0089587683f}, + { 0.0282322904f, 0.0282322904f}, + { 0.0063429899f, -0.0087303766f}, + { 0.0086257291f, 0.0043950285f}, + { 0.0050847711f, -0.0156493164f}, + { 0.0018022529f, 0.0002854488f}, + {-0.0000000000f, -0.0096778115f}, + {-0.0036173562f, 0.0005729329f}, + {-0.0011603659f, -0.0035712391f}, + {-0.0052564711f, 0.0026783058f}, + }, + { + {-0.0054614245f, -0.0027827348f}, /* Filter 6 */ + {-0.0020112222f, 0.0061899056f}, + { 0.0018009090f, 0.0002852360f}, + {-0.0000000000f, 0.0092685623f}, + { 0.0105861265f, -0.0016766777f}, + { 0.0032233168f, 0.0099203492f}, + { 0.0165731197f, -0.0084444263f}, + {-0.0047348689f, -0.0065169880f}, + { 0.0284660210f, -0.0284660210f}, + {-0.0606982839f, -0.0440998847f}, + { 0.0679608493f, -0.1333806768f}, + {-0.2215345589f, -0.0719809416f}, + { 0.1420835849f, -0.8970804494f}, + { 1.1095595360f, 0.0000000000f}, + { 0.0905183226f, 0.5715101964f}, + { 0.0256033627f, -0.0083190368f}, + { 0.0667555589f, 0.1310151612f}, + { 0.0011182680f, -0.0008124692f}, + { 0.0251126307f, 0.0251126307f}, + { 0.0088213528f, -0.0121415505f}, + { 0.0049297142f, 0.0025118148f}, + { 0.0051901643f, -0.0159736834f}, + {-0.0011144870f, -0.0001765174f}, + {-0.0000000000f, -0.0087472824f}, + {-0.0049435003f, 0.0007829735f}, + {-0.0007306011f, -0.0022485590f}, + {-0.0054614245f, 0.0027827348f}, + }, + { + {-0.0053826099f, -0.0027425768f}, /* Filter 7 */ + {-0.0020837980f, 0.0064132707f}, + { 0.0037729191f, 0.0005975717f}, + {-0.0000000000f, 0.0079766726f}, + { 0.0129897446f, -0.0020573734f}, + { 0.0021568809f, 0.0066381970f}, + { 0.0175076452f, -0.0089205908f}, + {-0.0090916622f, -0.0125135995f}, + { 0.0247376160f, -0.0247376159f}, + {-0.0774892752f, -0.0562992540f}, + { 0.0600222537f, -0.1178003057f}, + {-0.2992580667f, -0.0972348401f}, + { 0.1524445751f, -0.9624971666f}, + { 1.0763458014f, 0.0000000000f}, + { 0.0765237582f, 0.4831519944f}, + { 0.0675630592f, -0.0219525687f}, + { 0.0603186345f, 0.1183819857f}, + { 0.0125153543f, -0.0090929371f}, + { 0.0209691082f, 0.0209691082f}, + { 0.0105277637f, -0.0144902237f}, + { 0.0010806327f, 0.0005506098f}, + { 0.0050394423f, -0.0155098087f}, + {-0.0037772132f, -0.0005982518f}, + {-0.0000000000f, -0.0073941285f}, + {-0.0059141931f, 0.0009367162f}, + {-0.0002684621f, -0.0008262413f}, + {-0.0053826099f, 0.0027425768f}, + }, +}; + +/* Raised root cosine pulse shaping filter set; beta 0.5; sample rate 8000; 12 phase steps; + baud rate 1200; shifted to centre at 1800Hz; complex. */ +#define PULSESHAPER_2400_GAIN 2.223f +#define PULSESHAPER_2400_COEFF_SETS 12 +static const complexf_t pulseshaper_2400[PULSESHAPER_2400_COEFF_SETS][V27TER_RX_2400_FILTER_STEPS] = +{ + { + { 0.0036326320f, 0.0018509185f}, /* Filter 0 */ + { 0.0003793370f, -0.0011674794f}, + { 0.0048754563f, 0.0007721964f}, + { 0.0000000000f, -0.0062069190f}, + {-0.0027810383f, 0.0004404732f}, + {-0.0021925965f, -0.0067481182f}, + {-0.0140173459f, 0.0071421944f}, + { 0.0019772880f, 0.0027215034f}, + {-0.0092149554f, 0.0092149553f}, + { 0.0334995425f, 0.0243388423f}, + { 0.0199195813f, -0.0390943796f}, + { 0.1477459776f, 0.0480055782f}, + { 0.0427277333f, -0.2697722907f}, + { 1.0040582418f, 0.0000000000f}, + { 0.1570693140f, 0.9916966187f}, + {-0.2597668560f, 0.0844033680f}, + { 0.0705271128f, 0.1384172525f}, + {-0.0354969538f, 0.0257900466f}, + { 0.0292796738f, 0.0292796738f}, + { 0.0076599673f, -0.0105430406f}, + { 0.0029973132f, 0.0015272073f}, + { 0.0048614662f, -0.0149620544f}, + {-0.0070080354f, -0.0011099638f}, + {-0.0000000000f, -0.0028157043f}, + {-0.0061305015f, 0.0009709761f}, + { 0.0015253788f, 0.0046946332f}, + {-0.0010937644f, 0.0005573008f}, + }, + { + {-0.0002819961f, -0.0001436842f}, /* Filter 1 */ + { 0.0006588563f, -0.0020277512f}, + { 0.0041797109f, 0.0006620012f}, + { 0.0000000000f, -0.0065623410f}, + {-0.0042368606f, 0.0006710528f}, + {-0.0017245111f, -0.0053074995f}, + {-0.0146686673f, 0.0074740593f}, + { 0.0038644283f, 0.0053189292f}, + {-0.0067358415f, 0.0067358415f}, + { 0.0345347757f, 0.0250909833f}, + { 0.0269170677f, -0.0528277198f}, + { 0.1389398473f, 0.0451442930f}, + { 0.0525256151f, -0.3316336818f}, + { 1.0434222221f, 0.0000000000f}, + { 0.1499906453f, 0.9470036639f}, + {-0.2028542371f, 0.0659113371f}, + { 0.0727579142f, 0.1427954467f}, + {-0.0235379981f, 0.0171013566f}, + { 0.0275384769f, 0.0275384769f}, + { 0.0093041035f, -0.0128059998f}, + { 0.0001136455f, 0.0000579053f}, + { 0.0045116911f, -0.0138855574f}, + {-0.0082179267f, -0.0013015917f}, + {-0.0000000000f, -0.0013177606f}, + {-0.0055834514f, 0.0008843318f}, + { 0.0016945064f, 0.0052151545f}, + {-0.0002819961f, 0.0001436842f}, + }, + { + { 0.0005112062f, 0.0002604726f}, /* Filter 2 */ + { 0.0009277352f, -0.0028552754f}, + { 0.0033466091f, 0.0005300508f}, + { 0.0000000000f, -0.0067017064f}, + {-0.0056208495f, 0.0008902551f}, + {-0.0011771217f, -0.0036228080f}, + {-0.0149285967f, 0.0076064999f}, + { 0.0056743444f, 0.0078100650f}, + {-0.0038028170f, 0.0038028170f}, + { 0.0344837758f, 0.0250539297f}, + { 0.0340482504f, -0.0668234539f}, + { 0.1257304818f, 0.0408523100f}, + { 0.0626620127f, -0.3956323778f}, + { 1.0763765574f, 0.0000000000f}, + { 0.1420845360f, 0.8970864542f}, + {-0.1491315874f, 0.0484557901f}, + { 0.0731690629f, 0.1436023716f}, + {-0.0123276338f, 0.0089565502f}, + { 0.0250869159f, 0.0250869159f}, + { 0.0105070407f, -0.0144617008f}, + {-0.0027029676f, -0.0013772308f}, + { 0.0040530413f, -0.0124739786f}, + {-0.0091186142f, -0.0014442466f}, + { 0.0000000000f, 0.0001565015f}, + {-0.0048632493f, 0.0007702630f}, + { 0.0018111360f, 0.0055741034f}, + { 0.0005112062f, -0.0002604726f}, + }, + { + { 0.0012626700f, 0.0006433625f}, /* Filter 3 */ + { 0.0011774450f, -0.0036238031f}, + { 0.0023987660f, 0.0003799272f}, + { 0.0000000000f, -0.0066136620f}, + {-0.0068852097f, 0.0010905101f}, + {-0.0005635325f, -0.0017343746f}, + {-0.0147735044f, 0.0075274764f}, + { 0.0073440948f, 0.0101082792f}, + {-0.0004807357f, 0.0004807357f}, + { 0.0332365955f, 0.0241478001f}, + { 0.0411429005f, -0.0807474888f}, + { 0.1079056364f, 0.0350606666f}, + { 0.0730301872f, -0.4610944549f}, + { 1.1024802923f, 0.0000000000f}, + { 0.1334542238f, 0.8425968074f}, + {-0.0990668329f, 0.0321887653f}, + { 0.0719339457f, 0.1411783175f}, + {-0.0020666801f, 0.0015015310f}, + { 0.0220599213f, 0.0220599213f}, + { 0.0112587393f, -0.0154963252f}, + {-0.0053688554f, -0.0027355684f}, + { 0.0035031104f, -0.0107814651f}, + {-0.0096971580f, -0.0015358789f}, + { 0.0000000000f, 0.0015619067f}, + {-0.0039973434f, 0.0006331170f}, + { 0.0018730190f, 0.0057645597f}, + { 0.0012626700f, -0.0006433625f}, + }, + { + { 0.0019511001f, 0.0009941351f}, /* Filter 4 */ + { 0.0013998188f, -0.0043081992f}, + { 0.0013630316f, 0.0002158830f}, + { 0.0000000000f, -0.0062936717f}, + {-0.0079839961f, 0.0012645408f}, + { 0.0001005750f, 0.0003095379f}, + {-0.0141915007f, 0.0072309308f}, + { 0.0088117029f, 0.0121282686f}, + { 0.0031486956f, -0.0031486956f}, + { 0.0307069943f, 0.0223099373f}, + { 0.0480159027f, -0.0942365150f}, + { 0.0853178815f, 0.0277214601f}, + { 0.0835162618f, -0.5273009241f}, + { 1.1213819981f, 0.0000000000f}, + { 0.1242110958f, 0.7842379942f}, + {-0.0530547129f, 0.0172385212f}, + { 0.0692420091f, 0.1358950944f}, + { 0.0070828755f, -0.0051460103f}, + { 0.0185973391f, 0.0185973390f}, + { 0.0115630893f, -0.0159152271f}, + {-0.0078088406f, -0.0039788030f}, + { 0.0028814530f, -0.0088682003f}, + {-0.0099506630f, -0.0015760302f}, + { 0.0000000000f, 0.0028570218f}, + {-0.0030168074f, 0.0004778154f}, + { 0.0018796273f, 0.0057848981f}, + { 0.0019511001f, -0.0009941351f}, + }, + { + { 0.0025576725f, 0.0013031992f}, /* Filter 5 */ + { 0.0015873149f, -0.0048852529f}, + { 0.0002697804f, 0.0000427290f}, + { 0.0000000000f, -0.0057443897f}, + {-0.0088745956f, 0.0014055979f}, + { 0.0007973152f, 0.0024538838f}, + {-0.0131833524f, 0.0067172536f}, + { 0.0100180850f, 0.0137887111f}, + { 0.0069881240f, -0.0069881240f}, + { 0.0268364889f, 0.0194978505f}, + { 0.0544701590f, -0.1069037062f}, + { 0.0578903347f, 0.0188097100f}, + { 0.0940009470f, -0.5934986215f}, + { 1.1328263283f, 0.0000000000f}, + { 0.1144728051f, 0.7227528464f}, + {-0.0114127678f, 0.0037082330f}, + { 0.0652944573f, 0.1281475878f}, + { 0.0149986858f, -0.0108971831f}, + { 0.0148400450f, 0.0148400450f}, + { 0.0114369676f, -0.0157416354f}, + {-0.0099579979f, -0.0050738534f}, + { 0.0022089316f, -0.0067983924f}, + {-0.0098859888f, -0.0015657868f}, + { 0.0000000000f, 0.0040052626f}, + {-0.0019552917f, 0.0003096878f}, + { 0.0018321212f, 0.0056386894f}, + { 0.0025576725f, -0.0013031992f}, + }, + { + { 0.0030665390f, 0.0015624797f}, /* Filter 6 */ + { 0.0017332683f, -0.0053344513f}, + {-0.0008479269f, -0.0001342984f}, + { 0.0000000000f, -0.0049758209f}, + {-0.0095191676f, 0.0015076880f}, + { 0.0015070535f, 0.0046382337f}, + {-0.0117630486f, 0.0059935726f}, + { 0.0109089996f, 0.0150149499f}, + { 0.0109262557f, -0.0109262557f}, + { 0.0215979519f, 0.0156918306f}, + { 0.0602999226f, -0.1183452615f}, + { 0.0256212387f, 0.0083248451f}, + { 0.1043613218f, -0.6589114533f}, + { 1.1366584301f, 0.0000000000f}, + { 0.1043613218f, 0.6589114533f}, + { 0.0256212387f, -0.0083248451f}, + { 0.0602999226f, 0.1183452615f}, + { 0.0215979519f, -0.0156918306f}, + { 0.0109262557f, 0.0109262557f}, + { 0.0109089996f, -0.0150149499f}, + {-0.0117630486f, -0.0059935726f}, + { 0.0015070535f, -0.0046382337f}, + {-0.0095191676f, -0.0015076880f}, + { 0.0000000000f, 0.0049758209f}, + {-0.0008479269f, 0.0001342984f}, + { 0.0017332683f, 0.0053344513f}, + { 0.0030665390f, -0.0015624797f}, + }, + { + { 0.0034652296f, 0.0017656227f}, /* Filter 7 */ + { 0.0018321212f, -0.0056386894f}, + {-0.0019552917f, -0.0003096878f}, + { 0.0000000000f, -0.0040052626f}, + {-0.0098859888f, 0.0015657868f}, + { 0.0022089316f, 0.0067983924f}, + {-0.0099579979f, 0.0050738534f}, + { 0.0114369676f, 0.0157416354f}, + { 0.0148400450f, -0.0148400450f}, + { 0.0149986858f, 0.0108971831f}, + { 0.0652944573f, -0.1281475878f}, + {-0.0114127678f, -0.0037082330f}, + { 0.1144728051f, -0.7227528464f}, + { 1.1328263283f, 0.0000000000f}, + { 0.0940009470f, 0.5934986215f}, + { 0.0578903347f, -0.0188097100f}, + { 0.0544701590f, 0.1069037062f}, + { 0.0268364889f, -0.0194978505f}, + { 0.0069881240f, 0.0069881240f}, + { 0.0100180850f, -0.0137887111f}, + {-0.0131833524f, -0.0067172536f}, + { 0.0007973152f, -0.0024538838f}, + {-0.0088745956f, -0.0014055979f}, + { 0.0000000000f, 0.0057443897f}, + { 0.0002697804f, -0.0000427290f}, + { 0.0015873149f, 0.0048852529f}, + { 0.0034652296f, -0.0017656227f}, + }, + { + { 0.0037449420f, 0.0019081433f}, /* Filter 8 */ + { 0.0018796273f, -0.0057848981f}, + {-0.0030168074f, -0.0004778154f}, + { 0.0000000000f, -0.0028570218f}, + {-0.0099506630f, 0.0015760302f}, + { 0.0028814530f, 0.0088682003f}, + {-0.0078088406f, 0.0039788030f}, + { 0.0115630893f, 0.0159152271f}, + { 0.0185973391f, -0.0185973390f}, + { 0.0070828755f, 0.0051460103f}, + { 0.0692420091f, -0.1358950944f}, + {-0.0530547129f, -0.0172385212f}, + { 0.1242110958f, -0.7842379942f}, + { 1.1213819981f, 0.0000000000f}, + { 0.0835162618f, 0.5273009241f}, + { 0.0853178815f, -0.0277214601f}, + { 0.0480159027f, 0.0942365150f}, + { 0.0307069943f, -0.0223099373f}, + { 0.0031486956f, 0.0031486956f}, + { 0.0088117029f, -0.0121282686f}, + {-0.0141915007f, -0.0072309308f}, + { 0.0001005750f, -0.0003095379f}, + {-0.0079839961f, -0.0012645408f}, + { 0.0000000000f, 0.0062936717f}, + { 0.0013630316f, -0.0002158830f}, + { 0.0013998188f, 0.0043081992f}, + { 0.0037449420f, -0.0019081433f}, + }, + { + { 0.0039007144f, 0.0019875132f}, /* Filter 9 */ + { 0.0018730190f, -0.0057645597f}, + {-0.0039973434f, -0.0006331170f}, + { 0.0000000000f, -0.0015619067f}, + {-0.0096971580f, 0.0015358789f}, + { 0.0035031104f, 0.0107814651f}, + {-0.0053688554f, 0.0027355684f}, + { 0.0112587393f, 0.0154963252f}, + { 0.0220599213f, -0.0220599213f}, + {-0.0020666801f, -0.0015015310f}, + { 0.0719339457f, -0.1411783175f}, + {-0.0990668329f, -0.0321887653f}, + { 0.1334542238f, -0.8425968074f}, + { 1.1024802923f, 0.0000000000f}, + { 0.0730301872f, 0.4610944549f}, + { 0.1079056364f, -0.0350606666f}, + { 0.0411429005f, 0.0807474888f}, + { 0.0332365955f, -0.0241478001f}, + {-0.0004807357f, -0.0004807357f}, + { 0.0073440948f, -0.0101082792f}, + {-0.0147735044f, -0.0075274764f}, + {-0.0005635325f, 0.0017343746f}, + {-0.0068852097f, -0.0010905101f}, + { 0.0000000000f, 0.0066136620f}, + { 0.0023987660f, -0.0003799272f}, + { 0.0011774450f, 0.0036238031f}, + { 0.0039007144f, -0.0019875132f}, + }, + { + { 0.0039314768f, 0.0020031875f}, /* Filter 10 */ + { 0.0018111360f, -0.0055741034f}, + {-0.0048632493f, -0.0007702630f}, + { 0.0000000000f, -0.0001565015f}, + {-0.0091186142f, 0.0014442466f}, + { 0.0040530413f, 0.0124739786f}, + {-0.0027029676f, 0.0013772308f}, + { 0.0105070407f, 0.0144617008f}, + { 0.0250869159f, -0.0250869159f}, + {-0.0123276338f, -0.0089565502f}, + { 0.0731690629f, -0.1436023716f}, + {-0.1491315874f, -0.0484557901f}, + { 0.1420845360f, -0.8970864542f}, + { 1.0763765574f, 0.0000000000f}, + { 0.0626620127f, 0.3956323778f}, + { 0.1257304818f, -0.0408523100f}, + { 0.0340482504f, 0.0668234539f}, + { 0.0344837758f, -0.0250539297f}, + {-0.0038028170f, -0.0038028170f}, + { 0.0056743444f, -0.0078100650f}, + {-0.0149285967f, -0.0076064999f}, + {-0.0011771217f, 0.0036228080f}, + {-0.0056208495f, -0.0008902551f}, + { 0.0000000000f, 0.0067017064f}, + { 0.0033466091f, -0.0005300508f}, + { 0.0009277352f, 0.0028552754f}, + { 0.0039314768f, -0.0020031875f}, + }, + { + { 0.0038399827f, 0.0019565689f}, /* Filter 11 */ + { 0.0016945064f, -0.0052151545f}, + {-0.0055834514f, -0.0008843318f}, + {-0.0000000000f, 0.0013177606f}, + {-0.0082179267f, 0.0013015917f}, + { 0.0045116911f, 0.0138855574f}, + { 0.0001136455f, -0.0000579053f}, + { 0.0093041035f, 0.0128059998f}, + { 0.0275384769f, -0.0275384769f}, + {-0.0235379981f, -0.0171013566f}, + { 0.0727579142f, -0.1427954467f}, + {-0.2028542371f, -0.0659113371f}, + { 0.1499906453f, -0.9470036639f}, + { 1.0434222221f, 0.0000000000f}, + { 0.0525256151f, 0.3316336818f}, + { 0.1389398473f, -0.0451442930f}, + { 0.0269170677f, 0.0528277198f}, + { 0.0345347757f, -0.0250909833f}, + {-0.0067358415f, -0.0067358415f}, + { 0.0038644283f, -0.0053189292f}, + {-0.0146686673f, -0.0074740593f}, + {-0.0017245111f, 0.0053074995f}, + {-0.0042368606f, -0.0006710528f}, + { 0.0000000000f, 0.0065623410f}, + { 0.0041797109f, -0.0006620012f}, + { 0.0006588563f, 0.0020277512f}, + { 0.0038399827f, -0.0019565689f}, + }, +}; + +float v27ter_rx_carrier_frequency(v27ter_rx_state_t *s) +{ + return dds_frequencyf(s->carrier_phase_rate); +} +/*- End of function --------------------------------------------------------*/ + +float v27ter_rx_symbol_timing_correction(v27ter_rx_state_t *s) +{ + int steps_per_symbol; + + steps_per_symbol = (s->bit_rate == 4800) ? PULSESHAPER_4800_COEFF_SETS*5 : PULSESHAPER_2400_COEFF_SETS*20/3; + return (float) s->total_baud_timing_correction/(float) steps_per_symbol; +} +/*- End of function --------------------------------------------------------*/ + +float v27ter_rx_signal_power(v27ter_rx_state_t *s) +{ + return power_meter_dbm0(&s->power); +} +/*- End of function --------------------------------------------------------*/ + +void v27ter_rx_signal_cutoff(v27ter_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 --------------------------------------------------------*/ + +int v27ter_rx_equalizer_state(v27ter_rx_state_t *s, complexf_t **coeffs) +{ + *coeffs = s->eq_coeff; + return V27TER_EQUALIZER_PRE_LEN + 1 + V27TER_EQUALIZER_POST_LEN; +} +/*- End of function --------------------------------------------------------*/ + +static void equalizer_save(v27ter_rx_state_t *s) +{ + cvec_copyf(s->eq_coeff_save, s->eq_coeff, V27TER_EQUALIZER_PRE_LEN + 1 + V27TER_EQUALIZER_POST_LEN); +} +/*- End of function --------------------------------------------------------*/ + +static void equalizer_restore(v27ter_rx_state_t *s) +{ + cvec_copyf(s->eq_coeff, s->eq_coeff_save, V27TER_EQUALIZER_PRE_LEN + 1 + V27TER_EQUALIZER_POST_LEN); + cvec_zerof(s->eq_buf, V27TER_EQUALIZER_MASK); + + s->eq_put_step = (s->bit_rate == 4800) ? PULSESHAPER_4800_COEFF_SETS*5/2 : PULSESHAPER_2400_COEFF_SETS*20/(3*2); + s->eq_step = 0; + s->eq_delta = EQUALIZER_DELTA/(V27TER_EQUALIZER_PRE_LEN + 1 + V27TER_EQUALIZER_POST_LEN); +} +/*- End of function --------------------------------------------------------*/ + +static void equalizer_reset(v27ter_rx_state_t *s) +{ + /* Start with an equalizer based on everything being perfect */ + cvec_zerof(s->eq_coeff, V27TER_EQUALIZER_PRE_LEN + 1 + V27TER_EQUALIZER_POST_LEN); + s->eq_coeff[V27TER_EQUALIZER_PRE_LEN] = complex_setf(1.414f, 0.0f); + cvec_zerof(s->eq_buf, V27TER_EQUALIZER_MASK); + + s->eq_put_step = (s->bit_rate == 4800) ? PULSESHAPER_4800_COEFF_SETS*5/2 : PULSESHAPER_2400_COEFF_SETS*20/(3*2); + s->eq_step = 0; + s->eq_delta = EQUALIZER_DELTA/(V27TER_EQUALIZER_PRE_LEN + 1 + V27TER_EQUALIZER_POST_LEN); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ complexf_t equalizer_get(v27ter_rx_state_t *s) +{ + int i; + int p; + complexf_t z; + complexf_t z1; + + /* Get the next equalized value. */ + z = complex_setf(0.0, 0.0); + p = s->eq_step - 1; + for (i = 0; i < V27TER_EQUALIZER_PRE_LEN + 1 + V27TER_EQUALIZER_POST_LEN; i++) + { + p = (p - 1) & V27TER_EQUALIZER_MASK; + z1 = complex_mulf(&s->eq_coeff[i], &s->eq_buf[p]); + z = complex_addf(&z, &z1); + } + return z; +} +/*- End of function --------------------------------------------------------*/ + +static void tune_equalizer(v27ter_rx_state_t *s, const complexf_t *z, const complexf_t *target) +{ + int i; + int p; + complexf_t ez; + complexf_t z1; + + /* Find the x and y mismatch from the exact constellation position. */ + ez = complex_subf(target, z); + ez.re *= s->eq_delta; + ez.im *= s->eq_delta; + + p = s->eq_step - 1; + for (i = 0; i < V27TER_EQUALIZER_PRE_LEN + 1 + V27TER_EQUALIZER_POST_LEN; i++) + { + p = (p - 1) & V27TER_EQUALIZER_MASK; + z1 = complex_conjf(&s->eq_buf[p]); + z1 = complex_mulf(&ez, &z1); + s->eq_coeff[i] = complex_addf(&s->eq_coeff[i], &z1); + /* Leak a little to tame uncontrolled wandering */ + s->eq_coeff[i].re *= 0.9999f; + s->eq_coeff[i].im *= 0.9999f; + } +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void track_carrier(v27ter_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)); +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ int descramble(v27ter_rx_state_t *s, int in_bit) +{ + int out_bit; + + out_bit = (in_bit ^ (s->scramble_reg >> 5) ^ (s->scramble_reg >> 6)) & 1; + if (s->scrambler_pattern_count >= 33) + { + out_bit ^= 1; + s->scrambler_pattern_count = 0; + } + else + { + if (s->in_training > TRAINING_STAGE_NORMAL_OPERATION && s->in_training < TRAINING_STAGE_TEST_ONES) + { + s->scrambler_pattern_count = 0; + } + else + { + if ((((s->scramble_reg >> 7) ^ in_bit) & ((s->scramble_reg >> 8) ^ in_bit) & ((s->scramble_reg >> 11) ^ in_bit) & 1)) + s->scrambler_pattern_count = 0; + else + s->scrambler_pattern_count++; + } + } + s->scramble_reg <<= 1; + if (s->in_training > TRAINING_STAGE_NORMAL_OPERATION && s->in_training < TRAINING_STAGE_TEST_ONES) + s->scramble_reg |= out_bit; + else + s->scramble_reg |= in_bit; + return out_bit; +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void put_bit(v27ter_rx_state_t *s, int bit) +{ + int out_bit; + + bit &= 1; + + out_bit = descramble(s, bit); + + /* We need to strip the last part of the training before we let data + go to the application. */ + if (s->in_training == TRAINING_STAGE_NORMAL_OPERATION) + { + s->put_bit(s->user_data, out_bit); + } + else + { + //span_log(&s->logging, SPAN_LOG_FLOW, "Test bit %d\n", out_bit); + /* 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 --------------------------------------------------------*/ + +static __inline__ int find_quadrant(const complexf_t *z) +{ + 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 --------------------------------------------------------*/ + +static __inline__ int find_octant(complexf_t *z) +{ + float abs_re; + float abs_im; + int b1; + int b2; + int bits; + + /* Are we near an axis or a diagonal? */ + abs_re = fabsf(z->re); + abs_im = fabsf(z->im); + if (abs_im > abs_re*0.4142136 && abs_im < abs_re*2.4142136) + { + /* Split the space along the two axes. */ + b1 = (z->re < 0.0); + b2 = (z->im < 0.0); + bits = (b2 << 2) | ((b1 ^ b2) << 1) | 1; + } + else + { + /* Split the space along the two diagonals. */ + b1 = (z->im > z->re); + b2 = (z->im < -z->re); + bits = (b2 << 2) | ((b1 ^ b2) << 1); + } + return bits; +} +/*- End of function --------------------------------------------------------*/ + +static void decode_baud(v27ter_rx_state_t *s, complexf_t *z) +{ + static const uint8_t phase_steps_4800[8] = + { + 4, 0, 2, 6, 7, 3, 1, 5 + }; + static const uint8_t phase_steps_2400[4] = + { + 0, 2, 3, 1 + }; + int nearest; + int raw_bits; + + switch (s->bit_rate) + { + case 4800: + default: + nearest = find_octant(z); + raw_bits = phase_steps_4800[(nearest - s->constellation_state) & 7]; + put_bit(s, raw_bits); + put_bit(s, raw_bits >> 1); + put_bit(s, raw_bits >> 2); + s->constellation_state = nearest; + break; + case 2400: + nearest = find_quadrant(z); + raw_bits = phase_steps_2400[(nearest - s->constellation_state) & 3]; + put_bit(s, raw_bits); + put_bit(s, raw_bits >> 1); + s->constellation_state = nearest; + nearest <<= 1; + break; + } + track_carrier(s, z, &v27ter_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 = 100; + tune_equalizer(s, z, &v27ter_constellation[nearest]); + } +} +/*- End of function --------------------------------------------------------*/ + +static __inline__ void process_half_baud(v27ter_rx_state_t *s, const complexf_t *sample) +{ + static const int abab_pos[2] = + { + 0, 4 + }; + complexf_t z; + complexf_t zz; + float p; + float q; + int i; + int j; + int32_t angle; + int32_t ang; + + /* Add a sample to the equalizer's circular buffer, but don't calculate anything + at this time. */ + s->eq_buf[s->eq_step] = *sample; + s->eq_step = (s->eq_step + 1) & V27TER_EQUALIZER_MASK; + + /* On alternate insertions we have a whole baud, and must process it. */ + if ((s->baud_phase ^= 1)) + { + //span_log(&s->logging, SPAN_LOG_FLOW, "Samp, %f, %f, %f, -1, 0x%X\n", z.re, z.im, sqrt(z.re*z.re + z.im*z.im), s->eq_put_step); + return; + } + //span_log(&s->logging, SPAN_LOG_FLOW, "Samp, %f, %f, %f, 1, 0x%X\n", z.re, z.im, sqrt(z.re*z.re + z.im*z.im), s->eq_put_step); + + /* Perform a Gardner test for baud alignment */ + p = s->eq_buf[(s->eq_step - 3) & V27TER_EQUALIZER_MASK].re + - s->eq_buf[(s->eq_step - 1) & V27TER_EQUALIZER_MASK].re; + p *= s->eq_buf[(s->eq_step - 2) & V27TER_EQUALIZER_MASK].re; + + q = s->eq_buf[(s->eq_step - 3) & V27TER_EQUALIZER_MASK].im + - s->eq_buf[(s->eq_step - 1) & V27TER_EQUALIZER_MASK].im; + q *= s->eq_buf[(s->eq_step - 2) & V27TER_EQUALIZER_MASK].im; + + s->gardner_integrate += (p + q > 0.0) ? s->gardner_step : -s->gardner_step; + + if (abs(s->gardner_integrate) >= 256) + { + /* This integrate and dump approach avoids rapid changes of the equalizer put step. + Rapid changes, without hysteresis, are bad. They degrade the equalizer performance + when the true symbol boundary is close to a sample boundary. */ + //span_log(&s->logging, SPAN_LOG_FLOW, "Hop %d\n", s->gardner_integrate); + s->eq_put_step += (s->gardner_integrate/256); + s->total_baud_timing_correction += (s->gardner_integrate/256); + if (s->qam_report) + s->qam_report(s->qam_user_data, NULL, NULL, s->gardner_integrate); + s->gardner_integrate = 0; + } + //span_log(&s->logging, SPAN_LOG_FLOW, "Gardner=%10.5f 0x%X\n", p, s->eq_put_step); + + z = equalizer_get(s); + + //span_log(&s->logging, SPAN_LOG_FLOW, "Equalized symbol - %15.5f %15.5f\n", z.re, z.im); + switch (s->in_training) + { + case TRAINING_STAGE_NORMAL_OPERATION: + decode_baud(s, &z); + break; + case TRAINING_STAGE_SYMBOL_ACQUISITION: + /* Allow time for the Gardner algorithm to settle the baud timing */ + /* Don't start narrowing the bandwidth of the Gardner algorithm too early. + Some modems are a bit wobbly when they start sending the signal. Also, we start + this analysis before our filter buffers have completely filled. */ + if (++s->training_count >= 30) + { + s->gardner_step = 32; + s->in_training = TRAINING_STAGE_LOG_PHASE; + s->angles[0] = + s->start_angles[0] = arctan2(z.im, z.re); + } + break; + case TRAINING_STAGE_LOG_PHASE: + /* Record the current alternate phase angle */ + angle = arctan2(z.im, z.re); + s->angles[1] = + s->start_angles[1] = angle; + s->training_count = 1; + s->in_training = TRAINING_STAGE_WAIT_FOR_HOP; + break; + case TRAINING_STAGE_WAIT_FOR_HOP: + 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 ABAB 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 >= 3) + { + /* 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; + if (s->bit_rate == 4800) + s->carrier_phase_rate += ang/10; + else + s->carrier_phase_rate += 3*(ang/40); + } + span_log(&s->logging, SPAN_LOG_FLOW, "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->in_training = TRAINING_STAGE_PARKED; + s->put_bit(s->user_data, PUTBIT_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 += 0x80000000; + p = angle*2.0f*3.14159f/(65536.0f*65536.0f); + zz = complex_setf(cosf(p), -sinf(p)); + for (i = 0; i <= V27TER_EQUALIZER_MASK; i++) + s->eq_buf[i] = complex_mulf(&s->eq_buf[i], &zz); + s->carrier_phase += angle; + + s->gardner_step = 1; + /* We have just seen the first element of the scrambled sequence so skip it. */ + s->training_bc = 1; + s->training_bc ^= descramble(s, 1); + descramble(s, 1); + descramble(s, 1); + s->training_count = 1; + s->in_training = TRAINING_STAGE_TRAIN_ON_ABAB; + } + else if (++s->training_count > V27TER_TRAINING_SEG_3_LEN) + { + /* This is bogus. There are not this many bits in this section + of a real training sequence. */ + span_log(&s->logging, SPAN_LOG_FLOW, "Training failed (sequence failed)\n"); + /* Park this modem */ + s->in_training = TRAINING_STAGE_PARKED; + s->put_bit(s->user_data, PUTBIT_TRAINING_FAILED); + } + break; + case TRAINING_STAGE_TRAIN_ON_ABAB: + /* Train on the scrambled ABAB section */ + s->training_bc ^= descramble(s, 1); + descramble(s, 1); + descramble(s, 1); + s->constellation_state = abab_pos[s->training_bc]; + track_carrier(s, &z, &v27ter_constellation[s->constellation_state]); + tune_equalizer(s, &z, &v27ter_constellation[s->constellation_state]); + + if (++s->training_count >= V27TER_TRAINING_SEG_5_LEN) + { + s->constellation_state = (s->bit_rate == 4800) ? 4 : 2; + s->training_count = 0; + s->in_training = TRAINING_STAGE_TEST_ONES; + s->carrier_track_i = 400.0; + s->carrier_track_p = 1000000.0; + } + break; + case TRAINING_STAGE_TEST_ONES: + decode_baud(s, &z); + /* Measure the training error */ + if (s->bit_rate == 4800) + zz = complex_subf(&z, &v27ter_constellation[s->constellation_state]); + else + zz = complex_subf(&z, &v27ter_constellation[s->constellation_state << 1]); + s->training_error += powerf(&zz); + if (++s->training_count >= V27TER_TRAINING_SEG_6_LEN) + { + if ((s->bit_rate == 4800 && s->training_error < 1.0f) + || + (s->bit_rate == 2400 && s->training_error < 1.0f)) + { + /* We are up and running */ + span_log(&s->logging, SPAN_LOG_FLOW, "Training succeeded (constellation mismatch %f)\n", s->training_error); + s->put_bit(s->user_data, PUTBIT_TRAINING_SUCCEEDED); + /* Apply some lag to the carrier off condition, to ensure the last few bits get pushed through + the processing. */ + s->carrier_present = (s->bit_rate == 4800) ? 90 : 120; + s->in_training = 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 */ + s->in_training = TRAINING_STAGE_PARKED; + s->put_bit(s->user_data, PUTBIT_TRAINING_FAILED); + } + } + break; + case TRAINING_STAGE_PARKED: + /* We failed to train! */ + /* Park here until the carrier drops. */ + break; + } + if (s->qam_report) + { + s->qam_report(s->qam_user_data, + &z, + &v27ter_constellation[s->constellation_state], + s->constellation_state); + } +} +/*- End of function --------------------------------------------------------*/ + +int v27ter_rx(v27ter_rx_state_t *s, const int16_t amp[], int len) +{ + int i; + int j; + int step; + complexf_t z; + complexf_t zz; + complexf_t samplex; + int32_t power; + + if (s->bit_rate == 4800) + { + for (i = 0; i < len; i++) + { + s->rrc_filter[s->rrc_filter_step] = + s->rrc_filter[s->rrc_filter_step + V27TER_RX_4800_FILTER_STEPS] = amp[i]; + if (++s->rrc_filter_step >= V27TER_RX_4800_FILTER_STEPS) + s->rrc_filter_step = 0; + + /* 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. */ + power = power_meter_update(&(s->power), (amp[i] - s->last_sample) >> 1); + s->last_sample = amp[i]; + //span_log(&s->logging, SPAN_LOG_FLOW, "Power = %f\n", power_meter_dbm0(&(s->power))); + if (s->carrier_present) + { + /* Look for power below turnoff threshold to turn the carrier off */ + if (power < s->carrier_off_power) + { + if (--s->carrier_present <= 0) + { + /* Count down a short delay, to ensure we push the last + few bits through the filters before stopping. */ + v27ter_rx_restart(s, s->bit_rate, FALSE); + s->put_bit(s->user_data, PUTBIT_CARRIER_DOWN); + continue; + } + } + } + else + { + /* Look for power exceeding turnon threshold to turn the carrier on */ + if (power < s->carrier_on_power) + continue; + s->carrier_present = 1; + s->put_bit(s->user_data, PUTBIT_CARRIER_UP); + } + if (s->in_training != TRAINING_STAGE_PARKED) + { + /* Only spend effort processing this data if the modem is not + parked, after training failure. */ + z = dds_complexf(&(s->carrier_phase), s->carrier_phase_rate); + + /* Put things into the equalization buffer at T/2 rate. The Gardner algorithm + will fiddle the step to align this with the symbols. */ + if ((s->eq_put_step -= PULSESHAPER_4800_COEFF_SETS) <= 0) + { + if (s->in_training == TRAINING_STAGE_SYMBOL_ACQUISITION) + { + /* Only AGC during the initial training */ + s->agc_scaling = (1.0f/PULSESHAPER_4800_GAIN)*1.414f/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 bandband by complex mixing. + No further filtering, to remove mixer harmonics, is needed. */ + step = -s->eq_put_step; + if (step > PULSESHAPER_4800_COEFF_SETS - 1) + step = PULSESHAPER_4800_COEFF_SETS - 1; + s->eq_put_step += PULSESHAPER_4800_COEFF_SETS*5/2; + zz.re = pulseshaper_4800[step][0].re*s->rrc_filter[s->rrc_filter_step]; + zz.im = pulseshaper_4800[step][0].im*s->rrc_filter[s->rrc_filter_step]; + for (j = 1; j < V27TER_RX_4800_FILTER_STEPS; j++) + { + zz.re += pulseshaper_4800[step][j].re*s->rrc_filter[j + s->rrc_filter_step]; + zz.im += pulseshaper_4800[step][j].im*s->rrc_filter[j + s->rrc_filter_step]; + } + samplex.re = zz.re*s->agc_scaling; + samplex.im = zz.im*s->agc_scaling; + /* Shift to baseband - since this is done in a full complex form, the + result is clean, and requires no further filtering, apart from the + equalizer. */ + zz.re = samplex.re*z.re - samplex.im*z.im; + zz.im = -samplex.re*z.im - samplex.im*z.re; + process_half_baud(s, &zz); + } + } + } + } + else + { + for (i = 0; i < len; i++) + { + s->rrc_filter[s->rrc_filter_step] = + s->rrc_filter[s->rrc_filter_step + V27TER_RX_2400_FILTER_STEPS] = amp[i]; + if (++s->rrc_filter_step >= V27TER_RX_2400_FILTER_STEPS) + s->rrc_filter_step = 0; + + /* 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. */ + power = power_meter_update(&(s->power), (amp[i] - s->last_sample) >> 1); + s->last_sample = amp[i]; + //span_log(&s->logging, SPAN_LOG_FLOW, "Power = %f\n", power_meter_dbm0(&(s->power))); + if (s->carrier_present) + { + /* Look for power below turnoff threshold to turn the carrier off */ + if (power < s->carrier_off_power) + { + if (--s->carrier_present <= 0) + { + /* Count down a short delay, to ensure we push the last + few bits through the filters before stopping. */ + v27ter_rx_restart(s, s->bit_rate, FALSE); + s->put_bit(s->user_data, PUTBIT_CARRIER_DOWN); + continue; + } + } + } + else + { + /* Look for power exceeding turnon threshold to turn the carrier on */ + if (power < s->carrier_on_power) + continue; + s->carrier_present = 1; + s->put_bit(s->user_data, PUTBIT_CARRIER_UP); + } + if (s->in_training != TRAINING_STAGE_PARKED) + { + /* Only spend effort processing this data if the modem is not + parked, after training failure. */ + z = dds_complexf(&(s->carrier_phase), s->carrier_phase_rate); + + /* Put things into the equalization buffer at T/2 rate. The Gardner algorithm + will fiddle the step to align this with the symbols. */ + if ((s->eq_put_step -= PULSESHAPER_2400_COEFF_SETS) <= 0) + { + if (s->in_training == TRAINING_STAGE_SYMBOL_ACQUISITION) + { + /* Only AGC during the initial training */ + s->agc_scaling = (1.0f/PULSESHAPER_2400_GAIN)*1.414f/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 bandband by complex mixing. + No further filtering, to remove mixer harmonics, is needed. */ + step = -s->eq_put_step; + if (step > PULSESHAPER_2400_COEFF_SETS - 1) + step = PULSESHAPER_2400_COEFF_SETS - 1; + s->eq_put_step += PULSESHAPER_2400_COEFF_SETS*20/(3*2); + zz.re = pulseshaper_2400[step][0].re*s->rrc_filter[s->rrc_filter_step]; + zz.im = pulseshaper_2400[step][0].im*s->rrc_filter[s->rrc_filter_step]; + for (j = 1; j < V27TER_RX_2400_FILTER_STEPS; j++) + { + zz.re += pulseshaper_2400[step][j].re*s->rrc_filter[j + s->rrc_filter_step]; + zz.im += pulseshaper_2400[step][j].im*s->rrc_filter[j + s->rrc_filter_step]; + } + samplex.re = zz.re*s->agc_scaling; + samplex.im = zz.im*s->agc_scaling; + /* Shift to baseband - since this is done in a full complex form, the + result is clean, and requires no further filtering apart from the + equalizer. */ + zz.re = samplex.re*z.re - samplex.im*z.im; + zz.im = -samplex.re*z.im - samplex.im*z.re; + process_half_baud(s, &zz); + } + } + } + } + return 0; +} +/*- End of function --------------------------------------------------------*/ + +void v27ter_rx_set_put_bit(v27ter_rx_state_t *s, put_bit_func_t put_bit, void *user_data) +{ + s->put_bit = put_bit; + s->user_data = user_data; +} +/*- End of function --------------------------------------------------------*/ + +int v27ter_rx_restart(v27ter_rx_state_t *s, int rate, int old_train) +{ + span_log(&s->logging, SPAN_LOG_FLOW, "Restarting V.27ter\n"); + if (rate != 4800 && rate != 2400) + return -1; + s->bit_rate = rate; + + vec_zerof(s->rrc_filter, sizeof(s->rrc_filter)/sizeof(s->rrc_filter[0])); + s->rrc_filter_step = 0; + + s->scramble_reg = 0x3C; + s->scrambler_pattern_count = 0; + s->in_training = TRAINING_STAGE_SYMBOL_ACQUISITION; + s->training_bc = 0; + s->training_count = 0; + s->training_error = 0.0f; + s->carrier_present = 0; + + s->carrier_phase = 0; + //s->carrier_track_i = 100000.0f; + //s->carrier_track_p = 20000000.0f; + s->carrier_track_i = 200000.0f; + s->carrier_track_p = 10000000.0f; + 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); + s->agc_scaling = 0.0005f; + equalizer_reset(s); + } + s->eq_skip = 0; + s->last_sample = 0; + + s->gardner_integrate = 0; + s->total_baud_timing_correction = 0; + s->gardner_step = 512; + s->baud_phase = 0; + + return 0; +} +/*- End of function --------------------------------------------------------*/ + +v27ter_rx_state_t *v27ter_rx_init(v27ter_rx_state_t *s, int rate, put_bit_func_t put_bit, void *user_data) +{ + if (s == NULL) + { + if ((s = (v27ter_rx_state_t *) malloc(sizeof(*s))) == NULL) + return NULL; + } + memset(s, 0, sizeof(*s)); + v27ter_rx_signal_cutoff(s, -45.5f); + s->put_bit = put_bit; + s->user_data = user_data; + span_log_init(&s->logging, SPAN_LOG_NONE, NULL); + span_log_set_protocol(&s->logging, "V.27ter"); + + v27ter_rx_restart(s, rate, FALSE); + return s; +} +/*- End of function --------------------------------------------------------*/ + +int v27ter_rx_release(v27ter_rx_state_t *s) +{ + free(s); + return 0; +} +/*- End of function --------------------------------------------------------*/ + +void v27ter_rx_set_qam_report_handler(v27ter_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 ------------------------------------------------------------*/