diff spandsp-0.0.3/spandsp-0.0.3/src/v22bis_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/v22bis_rx.c	Fri Jun 25 16:00:21 2010 +0200
@@ -0,0 +1,1662 @@
+/*
+ * SpanDSP - a series of DSP components for telephony
+ *
+ * v22bis_rx.c - ITU V.22bis modem receive part
+ *
+ * Written by Steve Underwood <steveu@coppice.org>
+ *
+ * Copyright (C) 2004 Steve Underwood
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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: v22bis_rx.c,v 1.32 2006/11/19 14:07:26 steveu Exp $
+ */
+
+/*! \file */
+
+/* THIS IS A WORK IN PROGRESS - NOT YET FUNCTIONAL! */
+
+#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/vector_float.h"
+#include "spandsp/async.h"
+#include "spandsp/power_meter.h"
+#include "spandsp/arctan2.h"
+#include "spandsp/complex.h"
+#include "spandsp/dds.h"
+#include "spandsp/complex_filters.h"
+
+#include "spandsp/v29rx.h"
+#include "spandsp/v22bis.h"
+
+#define ms_to_symbols(t)    (((t)*600)/1000)
+
+#define EQUALIZER_DELTA     0.25f
+
+/*
+The basic method used by the V.22bis receiver is:
+
+    Put each sample into the pulse-shaping and phase shift filter buffer
+
+    At T/2 rate:
+        Filter and demodulate the contents of the input filter buffer, producing a sample
+        in the equalizer filter buffer.
+
+        Tune the symbol timing based on the latest 3 samples in the equalizer buffer. This
+        updates the decision points for taking the T/2 samples.
+
+        Equalize the contents of the equalizer buffer, producing a demodulated constellation
+        point.
+
+        Find the nearest constellation point to the received position. This is our received
+        symbol.
+
+        Tune the local carrier, based on the angular mismatch between the actual signal and
+        the decision.
+        
+        Tune the equalizer, based on the mismatch between the actual signal and the decision.
+
+        Descramble and output the bits represented by the decision.
+*/
+
+enum
+{
+    V22BIS_TRAINING_STAGE_NORMAL_OPERATION,
+    V22BIS_TRAINING_STAGE_SYMBOL_ACQUISITION,
+    V22BIS_TRAINING_STAGE_LOG_PHASE,
+    V22BIS_TRAINING_STAGE_UNSCRAMBLED_ONES,
+    V22BIS_TRAINING_STAGE_UNSCRAMBLED_0011,
+    V22BIS_TRAINING_STAGE_SCRAMBLED_ONES_AT_1200,
+    V22BIS_TRAINING_STAGE_SCRAMBLED_ONES_AT_2400,
+    V22BIS_TRAINING_STAGE_WAIT_FOR_START_1,
+    V22BIS_TRAINING_STAGE_WAIT_FOR_START_2,
+    V22BIS_TRAINING_STAGE_PARKED
+};
+
+/* Raised root cosine pulse shaping; Beta = 0.75; 4 symbols either
+   side of the centre. We cannot simplify this by using only half
+   the filter, as each variant are each skewed by a n/PULSESHAPER_COEFF_SETS of a
+   sample. Only one is symmetric. */
+//#define PULSESHAPER_GAIN        159.37711522f
+#define PULSESHAPER_GAIN        39.98830768f
+#define PULSESHAPER_COEFF_SETS  12
+static const complexf_t pulseshaper_1200[PULSESHAPER_COEFF_SETS][V22BIS_RX_FILTER_STEPS] =
+{
+    {
+        { 0.0079885982f, -0.0245863758f},       /* Filter 0 */
+        { 0.0511994623f, -0.0166357141f},
+        { 0.0660513043f,  0.0479890816f},
+        {-0.0000000000f,  0.1045738682f},
+        {-0.0949529186f,  0.0689873323f},
+        {-0.1091215536f, -0.0354557447f},
+        {-0.0284108873f, -0.0874397159f},
+        { 0.0266686007f, -0.0367061794f},
+        {-0.0269077960f, -0.0000000000f},
+        {-0.0733404681f, -0.1009444892f},
+        { 0.0759629756f, -0.2337899953f},
+        { 0.3664972484f, -0.1190821752f},
+        { 0.4341508448f,  0.3154290617f},
+        {-0.0000000001f,  0.6913219094f},
+        {-0.6796512008f,  0.4937954843f},
+        {-0.9258067012f, -0.3008128405f},
+        {-0.3345100582f, -1.0295161009f},
+        { 0.6816816330f, -0.9382542372f},
+        { 1.1997879744f,  0.0000000000f},
+        { 0.7052176595f,  0.9706488252f},
+        {-0.3583812416f,  1.1029839516f},
+        {-1.0295161009f,  0.3345100582f},
+        {-0.7875382304f, -0.5721800327f},
+        {-0.0000000001f, -0.8400950432f},
+        { 0.5592911839f, -0.4063488245f},
+        { 0.5103749037f,  0.1658308655f},
+        { 0.1190821752f,  0.3664972484f},
+        {-0.1444901675f,  0.1988736391f},
+        {-0.1247742549f,  0.0000000000f},
+        {-0.0158160049f, -0.0217688642f},
+        {-0.0140205137f,  0.0431507044f},
+        {-0.0874397159f,  0.0284108873f},
+        {-0.0928243399f, -0.0674408302f},
+        {-0.0000000000f, -0.1173682660f},
+        { 0.0846020356f, -0.0614669770f},
+        { 0.0776479617f,  0.0252293516f},
+        { 0.0166357141f,  0.0511994623f},
+    },
+    {
+        { 0.0086805914f, -0.0267161131f},       /* Filter 1 */
+        { 0.0534664392f, -0.0173722990f},
+        { 0.0678047314f,  0.0492630191f},
+        {-0.0000000000f,  0.1061014757f},
+        {-0.0953093544f,  0.0692462996f},
+        {-0.1081032082f, -0.0351248607f},
+        {-0.0275043193f, -0.0846495852f},
+        { 0.0237129591f, -0.0326380879f},
+        {-0.0341038331f, -0.0000000000f},
+        {-0.0787785500f, -0.1084293649f},
+        { 0.0793628618f, -0.2442537695f},
+        { 0.3781612813f, -0.1228720471f},
+        { 0.4445744753f,  0.3230022490f},
+        {-0.0000000001f,  0.7040774226f},
+        {-0.6892225146f,  0.5007494688f},
+        {-0.9354411960f, -0.3039432764f},
+        {-0.3369024098f, -1.0368790627f},
+        { 0.6845118403f, -0.9421496987f},
+        { 1.2013363838f,  0.0000000000f},
+        { 0.7041431069f,  0.9691697955f},
+        {-0.3568129838f,  1.0981574059f},
+        {-1.0219419003f,  0.3320490718f},
+        {-0.7792053819f, -0.5661258698f},
+        {-0.0000000001f, -0.8281568289f},
+        { 0.5489386916f, -0.3988272846f},
+        { 0.4981442094f,  0.1618568748f},
+        { 0.1153177992f,  0.3549116850f},
+        {-0.1380993575f,  0.1900774688f},
+        {-0.1156847477f,  0.0000000000f},
+        {-0.0116915042f, -0.0160919763f},
+        {-0.0155188413f,  0.0477620810f},
+        {-0.0900719389f,  0.0292661469f},
+        {-0.0935767666f, -0.0679875016f},
+        {-0.0000000000f, -0.1168202460f},
+        { 0.0833092555f, -0.0605277158f},
+        { 0.0755547658f,  0.0245492328f},
+        { 0.0158989299f,  0.0489318743f},
+    },
+    {
+        { 0.0093799150f, -0.0288684089f},       /* Filter 2 */
+        { 0.0557304025f, -0.0181079060f},
+        { 0.0695286095f,  0.0505154915f},
+        {-0.0000000000f,  0.1075555831f},
+        {-0.0955764726f,  0.0694403723f},
+        {-0.1069486886f, -0.0347497351f},
+        {-0.0265459362f, -0.0816999897f},
+        { 0.0206513591f, -0.0284241568f},
+        {-0.0414781198f, -0.0000000000f},
+        {-0.0843107775f, -0.1160438284f},
+        { 0.0828017443f, -0.2548375726f},
+        { 0.3898994923f, -0.1266860217f},
+        { 0.4550133944f,  0.3305865824f},
+        {-0.0000000001f,  0.7167864442f},
+        {-0.6987025142f,  0.5076370835f},
+        {-0.9449097514f, -0.3070197999f},
+        {-0.3392250240f, -1.0440272093f},
+        { 0.6871879101f, -0.9458330274f},
+        { 1.2026044130f,  0.0000000000f},
+        { 0.7029046416f,  0.9674652219f},
+        {-0.3551651835f,  1.0930860043f},
+        {-1.0141602755f,  0.3295206726f},
+        {-0.7707393169f, -0.5599749088f},
+        {-0.0000000001f, -0.8161166310f},
+        { 0.5385575294f, -0.3912849724f},
+        { 0.4859414399f,  0.1578919441f},
+        { 0.1115802750f,  0.3434087634f},
+        {-0.1317866892f,  0.1813888103f},
+        {-0.1067595631f,  0.0000000000f},
+        {-0.0076726158f, -0.0105604501f},
+        {-0.0169618148f,  0.0522031002f},
+        {-0.0925478712f,  0.0300706252f},
+        {-0.0942174047f, -0.0684529543f},
+        {-0.0000000000f, -0.1161677912f},
+        { 0.0819620788f, -0.0595489368f},
+        { 0.0734324455f,  0.0238596480f},
+        { 0.0151627110f,  0.0466660261f},
+    },
+    {
+        { 0.0100859674f, -0.0310414154f},       /* Filter 3 */
+        { 0.0579889156f, -0.0188417397f},
+        { 0.0712205172f,  0.0517447330f},
+        {-0.0000000000f,  0.1089330688f},
+        {-0.0957518741f,  0.0695678070f},
+        {-0.1056557149f, -0.0343296230f},
+        {-0.0255352631f, -0.0785894617f},
+        { 0.0174835511f, -0.0240640435f},
+        {-0.0490297712f, -0.0000000000f},
+        {-0.0899358466f, -0.1237860769f},
+        { 0.0862785354f, -0.2655380368f},
+        { 0.4017075598f, -0.1305226982f},
+        { 0.4654633999f,  0.3381789625f},
+        {-0.0000000001f,  0.7294434309f},
+        {-0.7080869079f,  0.5144552588f},
+        {-0.9542079568f, -0.3100409508f},
+        {-0.3414767087f, -1.0509572029f},
+        { 0.6897085309f, -0.9493023753f},
+        { 1.2035913467f,  0.0000000000f},
+        { 0.7015029192f,  0.9655358791f},
+        {-0.3534386754f,  1.0877723694f},
+        {-1.0061750412f,  0.3269260824f},
+        {-0.7621439099f, -0.5537299514f},
+        {-0.0000000001f, -0.8039799333f},
+        { 0.5281522870f, -0.3837251067f},
+        { 0.4737714827f,  0.1539376825f},
+        { 0.1078709438f,  0.3319926262f},
+        {-0.1255540103f,  0.1728102714f},
+        {-0.0980006009f,  0.0000000000f},
+        {-0.0037596615f, -0.0051747300f},
+        {-0.0183496606f,  0.0564744473f},
+        {-0.0948692262f,  0.0308248810f},
+        {-0.0947483405f, -0.0688387007f},
+        {-0.0000000000f, -0.1154139340f},
+        { 0.0805630460f, -0.0585324802f},
+        { 0.0712837577f,  0.0231614988f},
+        { 0.0144278063f,  0.0444042198f},
+    },
+    {
+        { 0.0107981311f, -0.0332332328f},       /* Filter 4 */
+        { 0.0602394938f, -0.0195729975f},
+        { 0.0728780255f,  0.0529489815f},
+        {-0.0000000000f,  0.1102307960f},
+        {-0.0958332121f,  0.0696269050f},
+        {-0.1042220443f, -0.0338637941f},
+        {-0.0244718455f, -0.0753165931f},
+        { 0.0142093524f, -0.0195574965f},
+        {-0.0567577891f, -0.0000000000f},
+        {-0.0956523716f, -0.1316542029f},
+        { 0.0897921249f, -0.2763517499f},
+        { 0.4135811031f, -0.1343806386f},
+        { 0.4759201407f,  0.3457762301f},
+        {-0.0000000001f,  0.7420427203f},
+        {-0.7173712850f,  0.5212007165f},
+        {-0.9633312821f, -0.3130052984f},
+        {-0.3436563909f, -1.0576655865f},
+        { 0.6920724511f, -0.9525560141f},
+        { 1.2042965889f,  0.0000000000f},
+        { 0.6999385953f,  0.9633828402f},
+        {-0.3516343236f,  1.0822191238f},
+        {-0.9979898930f,  0.3242665529f},
+        {-0.7534233332f, -0.5473940969f},
+        {-0.0000000001f, -0.7917523384f},
+        { 0.5177274346f, -0.3761509955f},
+        { 0.4616391361f,  0.1499956548f},
+        { 0.1041911170f,  0.3206672966f},
+        {-0.1194031686f,  0.1643443555f},
+        {-0.0894096494f,  0.0000000000f},
+        { 0.0000471013f,  0.0000648294f},
+        {-0.0196826290f,  0.0605769046f},
+        {-0.0970377848f,  0.0315294862f},
+        {-0.0951716974f, -0.0691462904f},
+        {-0.0000000000f, -0.1145617142f},
+        { 0.0791146755f, -0.0574801750f},
+        { 0.0691114515f,  0.0224556718f},
+        { 0.0136949494f,  0.0421487205f},
+    },
+    {
+        { 0.0115157729f, -0.0354419053f},       /* Filter 5 */
+        { 0.0624796189f, -0.0203008596f},
+        { 0.0744986683f,  0.0541264527f},
+        {-0.0000000000f,  0.1114456356f},
+        {-0.0958181471f,  0.0696159601f},
+        {-0.1026455015f, -0.0333515443f},
+        {-0.0233552568f, -0.0718800873f},
+        { 0.0108286384f, -0.0149043426f},
+        {-0.0646610633f, -0.0000000000f},
+        {-0.1014589071f, -0.1396462023f},
+        { 0.0933413655f, -0.2872751951f},
+        { 0.4255155921f, -0.1382583976f},
+        { 0.4863792956f,  0.3533752263f},
+        {-0.0000000001f,  0.7545788884f},
+        {-0.7265513539f,  0.5278704762f},
+        {-0.9722753763f, -0.3159114122f},
+        {-0.3457630277f, -1.0641491413f},
+        { 0.6942784786f, -0.9555923343f},
+        { 1.2047197819f,  0.0000000000f},
+        { 0.6982125044f,  0.9610070586f},
+        {-0.3497529626f,  1.0764288902f},
+        {-0.9896088243f,  0.3215433955f},
+        {-0.7445815802f, -0.5409702063f},
+        {-0.0000000001f, -0.7794392109f},
+        { 0.5072873235f, -0.3685658276f},
+        { 0.4495492578f,  0.1460674107f},
+        { 0.1005420759f,  0.3094367087f},
+        {-0.1133359149f,  0.1559935063f},
+        {-0.0809883848f,  0.0000000000f},
+        { 0.0037474802f,  0.0051579643f},
+        {-0.0209610034f,  0.0645113364f},
+        {-0.0990553871f,  0.0321850479f},
+        {-0.0954896435f, -0.0693772882f},
+        {-0.0000000000f, -0.1136142015f},
+        { 0.0776194856f, -0.0563938580f},
+        { 0.0669182166f,  0.0217430461f},
+        { 0.0129648596f,  0.0399017334f},
+    },
+    {
+        { 0.0122382389f, -0.0376654267f},       /* Filter 6 */
+        { 0.0647067279f, -0.0210244898f},
+        { 0.0760799870f,  0.0552753434f},
+        {-0.0000000000f,  0.1125744730f},
+        {-0.0957043841f,  0.0695333034f},
+        {-0.1009239629f, -0.0327921845f},
+        {-0.0221851002f, -0.0682787150f},
+        { 0.0073413476f, -0.0101044979f},
+        {-0.0727383792f, -0.0000000000f},
+        {-0.1073539481f, -0.1477600336f},
+        { 0.0969250873f, -0.2983047366f},
+        { 0.4375065267f, -0.1421544850f},
+        { 0.4968364835f,  0.3609728217f},
+        {-0.0000000001f,  0.7670462132f},
+        {-0.7356228828f,  0.5344613194f},
+        {-0.9810359478f, -0.3187578917f},
+        {-0.3477955461f, -1.0704045296f},
+        { 0.6963254809f, -0.9584097862f},
+        { 1.2048609257f,  0.0000000000f},
+        { 0.6963254809f,  0.9584097862f},
+        {-0.3477955461f,  1.0704045296f},
+        {-0.9810359478f,  0.3187578917f},
+        {-0.7356228828f, -0.5344613194f},
+        {-0.0000000001f, -0.7670462132f},
+        { 0.4968364835f, -0.3609728217f},
+        { 0.4375065267f,  0.1421544850f},
+        { 0.0969250873f,  0.2983047366f},
+        {-0.1073539481f,  0.1477600336f},
+        {-0.0727383792f,  0.0000000000f},
+        { 0.0073413476f,  0.0101044979f},
+        {-0.0221851002f,  0.0682787150f},
+        {-0.1009239629f,  0.0327921845f},
+        {-0.0957043841f, -0.0695333034f},
+        {-0.0000000000f, -0.1125744730f},
+        { 0.0760799870f, -0.0552753434f},
+        { 0.0647067279f,  0.0210244898f},
+        { 0.0122382389f,  0.0376654267f},
+    },
+    {
+        { 0.0129648596f, -0.0399017334f},       /* Filter 7 */
+        { 0.0669182166f, -0.0217430461f},
+        { 0.0776194856f,  0.0563938580f},
+        {-0.0000000000f,  0.1136142015f},
+        {-0.0954896435f,  0.0693772882f},
+        {-0.0990553871f, -0.0321850479f},
+        {-0.0209610034f, -0.0645113364f},
+        { 0.0037474802f, -0.0051579643f},
+        {-0.0809883848f, -0.0000000000f},
+        {-0.1133359149f, -0.1559935063f},
+        { 0.1005420759f, -0.3094367087f},
+        { 0.4495492578f, -0.1460674107f},
+        { 0.5072873235f,  0.3685658276f},
+        {-0.0000000001f,  0.7794392109f},
+        {-0.7445815802f,  0.5409702063f},
+        {-0.9896088243f, -0.3215433955f},
+        {-0.3497529626f, -1.0764288902f},
+        { 0.6982125044f, -0.9610070586f},
+        { 1.2047197819f,  0.0000000000f},
+        { 0.6942784786f,  0.9555923343f},
+        {-0.3457630277f,  1.0641491413f},
+        {-0.9722753763f,  0.3159114122f},
+        {-0.7265513539f, -0.5278704762f},
+        {-0.0000000001f, -0.7545788884f},
+        { 0.4863792956f, -0.3533752263f},
+        { 0.4255155921f,  0.1382583976f},
+        { 0.0933413655f,  0.2872751951f},
+        {-0.1014589071f,  0.1396462023f},
+        {-0.0646610633f,  0.0000000000f},
+        { 0.0108286384f,  0.0149043426f},
+        {-0.0233552568f,  0.0718800873f},
+        {-0.1026455015f,  0.0333515443f},
+        {-0.0958181471f, -0.0696159601f},
+        {-0.0000000000f, -0.1114456356f},
+        { 0.0744986683f, -0.0541264527f},
+        { 0.0624796189f,  0.0203008596f},
+        { 0.0115157729f,  0.0354419053f},
+    },
+    {
+        { 0.0136949494f, -0.0421487205f},       /* Filter 8 */
+        { 0.0691114515f, -0.0224556718f},
+        { 0.0791146755f,  0.0574801750f},
+        {-0.0000000000f,  0.1145617142f},
+        {-0.0951716974f,  0.0691462904f},
+        {-0.0970377848f, -0.0315294862f},
+        {-0.0196826290f, -0.0605769046f},
+        { 0.0000471013f, -0.0000648294f},
+        {-0.0894096494f, -0.0000000000f},
+        {-0.1194031686f, -0.1643443555f},
+        { 0.1041911170f, -0.3206672966f},
+        { 0.4616391361f, -0.1499956548f},
+        { 0.5177274346f,  0.3761509955f},
+        {-0.0000000001f,  0.7917523384f},
+        {-0.7534233332f,  0.5473940969f},
+        {-0.9979898930f, -0.3242665529f},
+        {-0.3516343236f, -1.0822191238f},
+        { 0.6999385953f, -0.9633828402f},
+        { 1.2042965889f,  0.0000000000f},
+        { 0.6920724511f,  0.9525560141f},
+        {-0.3436563909f,  1.0576655865f},
+        {-0.9633312821f,  0.3130052984f},
+        {-0.7173712850f, -0.5212007165f},
+        {-0.0000000001f, -0.7420427203f},
+        { 0.4759201407f, -0.3457762301f},
+        { 0.4135811031f,  0.1343806386f},
+        { 0.0897921249f,  0.2763517499f},
+        {-0.0956523716f,  0.1316542029f},
+        {-0.0567577891f,  0.0000000000f},
+        { 0.0142093524f,  0.0195574965f},
+        {-0.0244718455f,  0.0753165931f},
+        {-0.1042220443f,  0.0338637941f},
+        {-0.0958332121f, -0.0696269050f},
+        {-0.0000000000f, -0.1102307960f},
+        { 0.0728780255f, -0.0529489815f},
+        { 0.0602394938f,  0.0195729975f},
+        { 0.0107981311f,  0.0332332328f},
+    },
+    {
+        { 0.0144278063f, -0.0444042198f},       /* Filter 9 */
+        { 0.0712837577f, -0.0231614988f},
+        { 0.0805630460f,  0.0585324802f},
+        {-0.0000000000f,  0.1154139340f},
+        {-0.0947483405f,  0.0688387007f},
+        {-0.0948692262f, -0.0308248810f},
+        {-0.0183496606f, -0.0564744473f},
+        {-0.0037596615f,  0.0051747300f},
+        {-0.0980006009f, -0.0000000000f},
+        {-0.1255540103f, -0.1728102714f},
+        { 0.1078709438f, -0.3319926262f},
+        { 0.4737714827f, -0.1539376825f},
+        { 0.5281522870f,  0.3837251067f},
+        {-0.0000000001f,  0.8039799333f},
+        {-0.7621439099f,  0.5537299514f},
+        {-1.0061750412f, -0.3269260824f},
+        {-0.3534386754f, -1.0877723694f},
+        { 0.7015029192f, -0.9655358791f},
+        { 1.2035913467f,  0.0000000000f},
+        { 0.6897085309f,  0.9493023753f},
+        {-0.3414767087f,  1.0509572029f},
+        {-0.9542079568f,  0.3100409508f},
+        {-0.7080869079f, -0.5144552588f},
+        {-0.0000000001f, -0.7294434309f},
+        { 0.4654633999f, -0.3381789625f},
+        { 0.4017075598f,  0.1305226982f},
+        { 0.0862785354f,  0.2655380368f},
+        {-0.0899358466f,  0.1237860769f},
+        {-0.0490297712f,  0.0000000000f},
+        { 0.0174835511f,  0.0240640435f},
+        {-0.0255352631f,  0.0785894617f},
+        {-0.1056557149f,  0.0343296230f},
+        {-0.0957518741f, -0.0695678070f},
+        {-0.0000000000f, -0.1089330688f},
+        { 0.0712205172f, -0.0517447330f},
+        { 0.0579889156f,  0.0188417397f},
+        { 0.0100859674f,  0.0310414154f},
+    },
+    {
+        { 0.0151627110f, -0.0466660261f},       /* Filter 10 */
+        { 0.0734324455f, -0.0238596480f},
+        { 0.0819620788f,  0.0595489368f},
+        {-0.0000000000f,  0.1161677912f},
+        {-0.0942174047f,  0.0684529543f},
+        {-0.0925478712f, -0.0300706252f},
+        {-0.0169618148f, -0.0522031002f},
+        {-0.0076726158f,  0.0105604501f},
+        {-0.1067595631f, -0.0000000000f},
+        {-0.1317866892f, -0.1813888103f},
+        { 0.1115802750f, -0.3434087634f},
+        { 0.4859414399f, -0.1578919441f},
+        { 0.5385575294f,  0.3912849724f},
+        {-0.0000000001f,  0.8161166310f},
+        {-0.7707393169f,  0.5599749088f},
+        {-1.0141602755f, -0.3295206726f},
+        {-0.3551651835f, -1.0930860043f},
+        { 0.7029046416f, -0.9674652219f},
+        { 1.2026044130f,  0.0000000000f},
+        { 0.6871879101f,  0.9458330274f},
+        {-0.3392250240f,  1.0440272093f},
+        {-0.9449097514f,  0.3070197999f},
+        {-0.6987025142f, -0.5076370835f},
+        {-0.0000000001f, -0.7167864442f},
+        { 0.4550133944f, -0.3305865824f},
+        { 0.3898994923f,  0.1266860217f},
+        { 0.0828017443f,  0.2548375726f},
+        {-0.0843107775f,  0.1160438284f},
+        {-0.0414781198f,  0.0000000000f},
+        { 0.0206513591f,  0.0284241568f},
+        {-0.0265459362f,  0.0816999897f},
+        {-0.1069486886f,  0.0347497351f},
+        {-0.0955764726f, -0.0694403723f},
+        {-0.0000000000f, -0.1075555831f},
+        { 0.0695286095f, -0.0505154915f},
+        { 0.0557304025f,  0.0181079060f},
+        { 0.0093799150f,  0.0288684089f},
+    },
+    {
+        { 0.0158989299f, -0.0489318743f},       /* Filter 11 */
+        { 0.0755547658f, -0.0245492328f},
+        { 0.0833092555f,  0.0605277158f},
+        {-0.0000000000f,  0.1168202460f},
+        {-0.0935767666f,  0.0679875016f},
+        {-0.0900719389f, -0.0292661469f},
+        {-0.0155188413f, -0.0477620810f},
+        {-0.0116915042f,  0.0160919763f},
+        {-0.1156847477f, -0.0000000000f},
+        {-0.1380993575f, -0.1900774688f},
+        { 0.1153177992f, -0.3549116850f},
+        { 0.4981442094f, -0.1618568748f},
+        { 0.5489386916f,  0.3988272846f},
+        {-0.0000000001f,  0.8281568289f},
+        {-0.7792053819f,  0.5661258698f},
+        {-1.0219419003f, -0.3320490718f},
+        {-0.3568129838f, -1.0981574059f},
+        { 0.7041431069f, -0.9691697955f},
+        { 1.2013363838f,  0.0000000000f},
+        { 0.6845118403f,  0.9421496987f},
+        {-0.3369024098f,  1.0368790627f},
+        {-0.9354411960f,  0.3039432764f},
+        {-0.6892225146f, -0.5007494688f},
+        {-0.0000000001f, -0.7040774226f},
+        { 0.4445744753f, -0.3230022490f},
+        { 0.3781612813f,  0.1228720471f},
+        { 0.0793628618f,  0.2442537695f},
+        {-0.0787785500f,  0.1084293649f},
+        {-0.0341038331f,  0.0000000000f},
+        { 0.0237129591f,  0.0326380879f},
+        {-0.0275043193f,  0.0846495852f},
+        {-0.1081032082f,  0.0351248607f},
+        {-0.0953093544f, -0.0692462996f},
+        {-0.0000000000f, -0.1061014757f},
+        { 0.0678047314f, -0.0492630191f},
+        { 0.0534664392f,  0.0173722990f},
+        { 0.0086805914f,  0.0267161131f},
+    },
+};
+
+static const complexf_t pulseshaper_2400[PULSESHAPER_COEFF_SETS][V22BIS_RX_FILTER_STEPS] =
+{
+    {
+        { 0.0209144205f,  0.0151952161f},       /* Filter 0 */
+        {-0.0435528643f,  0.0316430070f},
+        {-0.0252293516f, -0.0776479617f},
+        { 0.1045738682f,  0.0000000001f},
+        {-0.0362687893f,  0.1116238534f},
+        {-0.0928243399f, -0.0674408302f},
+        { 0.0743806660f, -0.0540407188f},
+        { 0.0140205137f,  0.0431507044f},
+        { 0.0269077960f,  0.0000000000f},
+        {-0.0385573655f,  0.1186673641f},
+        {-0.1988736391f, -0.1444901675f},
+        { 0.3117611706f, -0.2265077531f},
+        { 0.1658308655f,  0.5103749037f},
+        {-0.6913219094f, -0.0000000002f},
+        { 0.2596036494f, -0.7989778519f},
+        { 0.7875382304f,  0.5721800327f},
+        {-0.8757586479f,  0.6362759471f},
+        {-0.3583812416f, -1.1029839516f},
+        { 1.1997879744f,  0.0000000000f},
+        {-0.3707548678f,  1.1410661936f},
+        {-0.9382542372f, -0.6816816330f},
+        { 0.8757586479f, -0.6362759471f},
+        { 0.3008128405f,  0.9258067012f},
+        {-0.8400950432f,  0.0000000002f},
+        { 0.2136302292f, -0.6574862003f},
+        { 0.4341508448f,  0.3154290617f},
+        {-0.3117611706f,  0.2265077531f},
+        {-0.0759629756f, -0.2337899953f},
+        { 0.1247742549f, -0.0000000001f},
+        {-0.0083149662f,  0.0255908351f},
+        { 0.0367061794f,  0.0266686007f},
+        {-0.0743806660f,  0.0540407188f},
+        {-0.0354557447f, -0.1091215536f},
+        { 0.1173682660f, -0.0000000001f},
+        {-0.0323151015f,  0.0994556621f},
+        {-0.0660513043f, -0.0479890816f},
+        { 0.0435528643f, -0.0316430070f},
+    },
+    {
+        { 0.0227260832f,  0.0165114664f},       /* Filter 1 */
+        {-0.0454812683f,  0.0330440775f},
+        {-0.0258991010f, -0.0797092393f},
+        { 0.1061014757f,  0.0000000001f},
+        {-0.0364049338f,  0.1120428666f},
+        {-0.0919580832f, -0.0668114573f},
+        { 0.0720072389f, -0.0523163229f},
+        { 0.0124666411f,  0.0383683741f},
+        { 0.0341038331f,  0.0000000000f},
+        {-0.0414163321f,  0.1274663657f},
+        {-0.2077746689f, -0.1509571373f},
+        { 0.3216831982f, -0.2337165177f},
+        { 0.1698123366f,  0.5226286054f},
+        {-0.7040774226f, -0.0000000002f},
+        { 0.2632595599f, -0.8102296591f},
+        { 0.7957338095f,  0.5781344771f},
+        {-0.8820219636f,  0.6408264637f},
+        {-0.3598691821f, -1.1075633764f},
+        { 1.2013363838f,  0.0000000000f},
+        {-0.3701899350f,  1.1393274069f},
+        {-0.9341484904f, -0.6786985993f},
+        { 0.8693157434f, -0.6315948367f},
+        { 0.2976299822f,  0.9160109162f},
+        {-0.8281568289f,  0.0000000002f},
+        { 0.2096759230f, -0.6453161240f},
+        { 0.4237467945f,  0.3078700602f},
+        {-0.3019059300f,  0.2193474919f},
+        {-0.0726031289f, -0.2234494686f},
+        { 0.1156847477f, -0.0000000001f},
+        {-0.0061465879f,  0.0189172514f},
+        { 0.0406288542f,  0.0295185894f},
+        {-0.0766197667f,  0.0556675196f},
+        {-0.0357431434f, -0.1100060865f},
+        { 0.1168202460f, -0.0000000001f},
+        {-0.0318213031f,  0.0979359001f},
+        {-0.0642707273f, -0.0466954149f},
+        { 0.0416239388f, -0.0302415621f},
+    },
+    {
+        { 0.0245569348f,  0.0178416576f},       /* Filter 2 */
+        {-0.0474071130f,  0.0344432816f},
+        {-0.0265575647f, -0.0817357823f},
+        { 0.1075555831f,  0.0000000001f},
+        {-0.0365069620f,  0.1123568788f},
+        {-0.0909759924f, -0.0660979301f},
+        { 0.0694981664f, -0.0504933707f},
+        { 0.0108570615f,  0.0334145986f},
+        { 0.0414781198f,  0.0000000000f},
+        {-0.0443248004f,  0.1364177018f},
+        {-0.2167777866f, -0.1574982852f},
+        { 0.3316683173f, -0.2409711480f},
+        { 0.1737996489f,  0.5349003077f},
+        {-0.7167864442f, -0.0000000002f},
+        { 0.2668806314f, -0.8213740587f},
+        { 0.8037882447f,  0.5839863420f},
+        {-0.8881026506f,  0.6452443004f},
+        {-0.3612760901f, -1.1118934155f},
+        { 1.2026044130f,  0.0000000000f},
+        {-0.3695388138f,  1.1373236179f},
+        {-0.9298345447f, -0.6755643487f},
+        { 0.8626962900f, -0.6267855763f},
+        { 0.2943962216f,  0.9060583711f},
+        {-0.8161166310f,  0.0000000002f},
+        { 0.2057106793f, -0.6331123710f},
+        { 0.4133664668f,  0.3003283143f},
+        {-0.2921209633f,  0.2122382969f},
+        {-0.0692843646f, -0.2132353336f},
+        { 0.1067595631f, -0.0000000001f},
+        {-0.0040337327f,  0.0124145532f},
+        { 0.0444066115f,  0.0322632901f},
+        {-0.0787259191f,  0.0571977310f},
+        {-0.0359878466f, -0.1107592061f},
+        { 0.1161677912f, -0.0000000001f},
+        {-0.0313067287f,  0.0963522047f},
+        {-0.0624653697f, -0.0453837477f},
+        { 0.0396964923f, -0.0288411900f},
+    },
+    {
+        { 0.0264054053f,  0.0191846490f},       /* Filter 3 */
+        {-0.0493283160f,  0.0358391218f},
+        {-0.0272038169f, -0.0837247372f},
+        { 0.1089330688f,  0.0000000001f},
+        {-0.0365739614f,  0.1125630811f},
+        {-0.0898761228f, -0.0652988255f},
+        { 0.0668521896f, -0.0485709570f},
+        { 0.0091916462f,  0.0282889791f},
+        { 0.0490297712f,  0.0000000000f},
+        {-0.0472820736f,  0.1455192566f},
+        {-0.2258801460f, -0.1641115248f},
+        { 0.3417128623f, -0.2482689321f},
+        { 0.1777911931f,  0.5471850634f},
+        {-0.7294434309f, -0.0000000002f},
+        { 0.2704651356f, -0.8324060440f},
+        { 0.8116977811f,  0.5897329450f},
+        {-0.8939976096f,  0.6495273113f},
+        {-0.3626012504f, -1.1159719229f},
+        { 1.2035913467f,  0.0000000000f},
+        {-0.3688018918f,  1.1350555420f},
+        {-0.9253144860f, -0.6722803116f},
+        { 0.8559035659f, -0.6218503714f},
+        { 0.2911130786f,  0.8959538937f},
+        {-0.8039799333f,  0.0000000002f},
+        { 0.2017362267f, -0.6208803058f},
+        { 0.4030140936f,  0.2928068638f},
+        {-0.2824097872f,  0.2051827312f},
+        {-0.0660076514f, -0.2031506598f},
+        { 0.0980006009f, -0.0000000001f},
+        {-0.0019765710f,  0.0060832603f},
+        { 0.0480400361f,  0.0349031277f},
+        {-0.0807005838f,  0.0586324073f},
+        {-0.0361906476f, -0.1113833562f},
+        { 0.1154139340f, -0.0000000001f},
+        {-0.0307723451f,  0.0947075412f},
+        {-0.0606375895f, -0.0440557860f},
+        { 0.0377724878f, -0.0274433177f},
+    },
+    {
+        { 0.0282698758f,  0.0205392670f},       /* Filter 4 */
+        {-0.0512427762f,  0.0372300558f},
+        {-0.0278369281f, -0.0856732503f},
+        { 0.1102307960f,  0.0000000001f},
+        {-0.0366050303f,  0.1126586944f},
+        {-0.0886565670f, -0.0644127652f},
+        { 0.0640681237f, -0.0465482138f},
+        { 0.0074702986f,  0.0229912158f},
+        { 0.0567577891f,  0.0000000000f},
+        {-0.0502874292f,  0.1547687948f},
+        {-0.2350788414f, -0.1707947701f},
+        { 0.3518131077f, -0.2556071877f},
+        { 0.1817853153f,  0.5594776869f},
+        {-0.7420427203f, -0.0000000002f},
+        { 0.2740114331f, -0.8433204889f},
+        { 0.8194585443f,  0.5953714848f},
+        {-0.8997041583f,  0.6536732912f},
+        {-0.3638440073f, -1.1197967529f},
+        { 1.2042965889f,  0.0000000000f},
+        {-0.3679794967f,  1.1325244904f},
+        {-0.9205905795f, -0.6688482165f},
+        { 0.8489409089f, -0.6167916656f},
+        { 0.2877821028f,  0.8857022524f},
+        {-0.7917523384f,  0.0000000002f},
+        { 0.1977542788f, -0.6086250544f},
+        { 0.3926937282f,  0.2853086889f},
+        {-0.2727758884f,  0.1981832832f},
+        {-0.0627739578f, -0.1931983829f},
+        { 0.0894096494f, -0.0000000000f},
+        { 0.0000247626f, -0.0000762115f},
+        { 0.0515297912f,  0.0374385864f},
+        {-0.0825452656f,  0.0599726476f},
+        {-0.0363523550f, -0.1118810475f},
+        { 0.1145617142f, -0.0000000001f},
+        {-0.0302191172f,  0.0930048823f},
+        {-0.0587897114f, -0.0427132249f},
+        { 0.0358538441f, -0.0260493420f},
+    },
+    {
+        { 0.0301486850f,  0.0219043009f},       /* Filter 5 */
+        {-0.0531483367f,  0.0386145264f},
+        {-0.0284559596f, -0.0875784382f},
+        { 0.1114456356f,  0.0000000001f},
+        {-0.0365992747f,  0.1126409844f},
+        {-0.0873154774f, -0.0634384081f},
+        { 0.0611448549f, -0.0444243364f},
+        { 0.0056929523f,  0.0175211057f},
+        { 0.0646610633f,  0.0000000000f},
+        {-0.0533401072f,  0.1641639620f},
+        {-0.2443708777f, -0.1775458306f},
+        { 0.3619651794f, -0.2629830837f},
+        { 0.1857803613f,  0.5717731118f},
+        {-0.7545788884f, -0.0000000002f},
+        { 0.2775179148f, -0.8541123271f},
+        { 0.8270668387f,  0.6008992195f},
+        {-0.9052193165f,  0.6576803327f},
+        {-0.3650037944f, -1.1233661175f},
+        { 1.2047197819f,  0.0000000000f},
+        {-0.3670720458f,  1.1297315359f},
+        {-0.9156650901f, -0.6652696729f},
+        { 0.8418115377f, -0.6116119027f},
+        { 0.2844048440f,  0.8753081560f},
+        {-0.7794392109f,  0.0000000002f},
+        { 0.1937665194f, -0.5963520408f},
+        { 0.3824094236f,  0.2778367102f},
+        {-0.2632225752f,  0.1912423968f},
+        {-0.0595842153f, -0.1833813637f},
+        { 0.0809883848f, -0.0000000000f},
+        { 0.0019701670f, -0.0060635507f},
+        { 0.0548766218f,  0.0398701988f},
+        {-0.0842615440f,  0.0612195991f},
+        {-0.0364737995f, -0.1122548133f},
+        { 0.1136142015f, -0.0000000001f},
+        {-0.0296480060f,  0.0912471786f},
+        {-0.0569240339f, -0.0413577296f},
+        { 0.0339424424f, -0.0246606283f},
+    },
+    {
+        { 0.0320401229f,  0.0232785121f},       /* Filter 6 */
+        {-0.0550428294f,  0.0399909541f},
+        {-0.0290599689f, -0.0894373879f},
+        { 0.1125744730f,  0.0000000001f},
+        {-0.0365558229f,  0.1125072464f},
+        {-0.0858510509f, -0.0623744428f},
+        { 0.0580813438f, -0.0421985686f},
+        { 0.0038595749f,  0.0118785501f},
+        { 0.0727383792f,  0.0000000000f},
+        {-0.0564393103f,  0.1737023294f},
+        {-0.2537531555f, -0.1843624711f},
+        { 0.3721652627f, -0.2703939080f},
+        { 0.1897746474f,  0.5840663314f},
+        {-0.7670462132f, -0.0000000002f},
+        { 0.2809829414f, -0.8647765517f},
+        { 0.8345190287f,  0.6063135862f},
+        {-0.9105405211f,  0.6615464091f},
+        {-0.3660799563f, -1.1266783476f},
+        { 1.2048609257f,  0.0000000000f},
+        {-0.3660799563f,  1.1266783476f},
+        {-0.9105405211f, -0.6615464091f},
+        { 0.8345190287f, -0.6063135862f},
+        { 0.2809829414f,  0.8647765517f},
+        {-0.7670462132f,  0.0000000002f},
+        { 0.1897746474f, -0.5840663314f},
+        { 0.3721652627f,  0.2703939080f},
+        {-0.2537531555f,  0.1843624711f},
+        {-0.0564393103f, -0.1737023294f},
+        { 0.0727383792f, -0.0000000000f},
+        { 0.0038595749f, -0.0118785501f},
+        { 0.0580813438f,  0.0421985686f},
+        {-0.0858510509f,  0.0623744428f},
+        {-0.0365558229f, -0.1125072464f},
+        { 0.1125744730f, -0.0000000001f},
+        {-0.0290599689f,  0.0894373879f},
+        {-0.0550428294f, -0.0399909541f},
+        { 0.0320401229f, -0.0232785121f},
+    },
+    {
+        { 0.0339424424f,  0.0246606283f},       /* Filter 7 */
+        {-0.0569240339f,  0.0413577296f},
+        {-0.0296480060f, -0.0912471786f},
+        { 0.1136142015f,  0.0000000001f},
+        {-0.0364737995f,  0.1122548133f},
+        {-0.0842615440f, -0.0612195991f},
+        { 0.0548766218f, -0.0398701988f},
+        { 0.0019701670f,  0.0060635507f},
+        { 0.0809883848f,  0.0000000000f},
+        {-0.0595842153f,  0.1833813637f},
+        {-0.2632225752f, -0.1912423968f},
+        { 0.3824094236f, -0.2778367102f},
+        { 0.1937665194f,  0.5963520408f},
+        {-0.7794392109f, -0.0000000002f},
+        { 0.2844048440f, -0.8753081560f},
+        { 0.8418115377f,  0.6116119027f},
+        {-0.9156650901f,  0.6652696729f},
+        {-0.3670720458f, -1.1297315359f},
+        { 1.2047197819f,  0.0000000000f},
+        {-0.3650037944f,  1.1233661175f},
+        {-0.9052193165f, -0.6576803327f},
+        { 0.8270668387f, -0.6008992195f},
+        { 0.2775179148f,  0.8541123271f},
+        {-0.7545788884f,  0.0000000002f},
+        { 0.1857803613f, -0.5717731118f},
+        { 0.3619651794f,  0.2629830837f},
+        {-0.2443708777f,  0.1775458306f},
+        {-0.0533401072f, -0.1641639620f},
+        { 0.0646610633f, -0.0000000000f},
+        { 0.0056929523f, -0.0175211057f},
+        { 0.0611448549f,  0.0444243364f},
+        {-0.0873154774f,  0.0634384081f},
+        {-0.0365992747f, -0.1126409844f},
+        { 0.1114456356f, -0.0000000001f},
+        {-0.0284559596f,  0.0875784382f},
+        {-0.0531483367f, -0.0386145264f},
+        { 0.0301486850f, -0.0219043009f},
+    },
+    {
+        { 0.0358538441f,  0.0260493420f},       /* Filter 8 */
+        {-0.0587897114f,  0.0427132249f},
+        {-0.0302191172f, -0.0930048823f},
+        { 0.1145617142f,  0.0000000001f},
+        {-0.0363523550f,  0.1118810475f},
+        {-0.0825452656f, -0.0599726476f},
+        { 0.0515297912f, -0.0374385864f},
+        { 0.0000247626f,  0.0000762115f},
+        { 0.0894096494f,  0.0000000000f},
+        {-0.0627739578f,  0.1931983829f},
+        {-0.2727758884f, -0.1981832832f},
+        { 0.3926937282f, -0.2853086889f},
+        { 0.1977542788f,  0.6086250544f},
+        {-0.7917523384f, -0.0000000002f},
+        { 0.2877821028f, -0.8857022524f},
+        { 0.8489409089f,  0.6167916656f},
+        {-0.9205905795f,  0.6688482165f},
+        {-0.3679794967f, -1.1325244904f},
+        { 1.2042965889f,  0.0000000000f},
+        {-0.3638440073f,  1.1197967529f},
+        {-0.8997041583f, -0.6536732912f},
+        { 0.8194585443f, -0.5953714848f},
+        { 0.2740114331f,  0.8433204889f},
+        {-0.7420427203f,  0.0000000002f},
+        { 0.1817853153f, -0.5594776869f},
+        { 0.3518131077f,  0.2556071877f},
+        {-0.2350788414f,  0.1707947701f},
+        {-0.0502874292f, -0.1547687948f},
+        { 0.0567577891f, -0.0000000000f},
+        { 0.0074702986f, -0.0229912158f},
+        { 0.0640681237f,  0.0465482138f},
+        {-0.0886565670f,  0.0644127652f},
+        {-0.0366050303f, -0.1126586944f},
+        { 0.1102307960f, -0.0000000001f},
+        {-0.0278369281f,  0.0856732503f},
+        {-0.0512427762f, -0.0372300558f},
+        { 0.0282698758f, -0.0205392670f},
+    },
+    {
+        { 0.0377724878f,  0.0274433177f},       /* Filter 9 */
+        {-0.0606375895f,  0.0440557860f},
+        {-0.0307723451f, -0.0947075412f},
+        { 0.1154139340f,  0.0000000001f},
+        {-0.0361906476f,  0.1113833562f},
+        {-0.0807005838f, -0.0586324073f},
+        { 0.0480400361f, -0.0349031277f},
+        {-0.0019765710f, -0.0060832603f},
+        { 0.0980006009f,  0.0000000001f},
+        {-0.0660076514f,  0.2031506598f},
+        {-0.2824097872f, -0.2051827312f},
+        { 0.4030140936f, -0.2928068638f},
+        { 0.2017362267f,  0.6208803058f},
+        {-0.8039799333f, -0.0000000002f},
+        { 0.2911130786f, -0.8959538937f},
+        { 0.8559035659f,  0.6218503714f},
+        {-0.9253144860f,  0.6722803116f},
+        {-0.3688018918f, -1.1350555420f},
+        { 1.2035913467f,  0.0000000000f},
+        {-0.3626012504f,  1.1159719229f},
+        {-0.8939976096f, -0.6495273113f},
+        { 0.8116977811f, -0.5897329450f},
+        { 0.2704651356f,  0.8324060440f},
+        {-0.7294434309f,  0.0000000002f},
+        { 0.1777911931f, -0.5471850634f},
+        { 0.3417128623f,  0.2482689321f},
+        {-0.2258801460f,  0.1641115248f},
+        {-0.0472820736f, -0.1455192566f},
+        { 0.0490297712f, -0.0000000000f},
+        { 0.0091916462f, -0.0282889791f},
+        { 0.0668521896f,  0.0485709570f},
+        {-0.0898761228f,  0.0652988255f},
+        {-0.0365739614f, -0.1125630811f},
+        { 0.1089330688f, -0.0000000001f},
+        {-0.0272038169f,  0.0837247372f},
+        {-0.0493283160f, -0.0358391218f},
+        { 0.0264054053f, -0.0191846490f},
+    },
+    {
+        { 0.0396964923f,  0.0288411900f},       /* Filter 10 */
+        {-0.0624653697f,  0.0453837477f},
+        {-0.0313067287f, -0.0963522047f},
+        { 0.1161677912f,  0.0000000001f},
+        {-0.0359878466f,  0.1107592061f},
+        {-0.0787259191f, -0.0571977310f},
+        { 0.0444066115f, -0.0322632901f},
+        {-0.0040337327f, -0.0124145532f},
+        { 0.1067595631f,  0.0000000001f},
+        {-0.0692843646f,  0.2132353336f},
+        {-0.2921209633f, -0.2122382969f},
+        { 0.4133664668f, -0.3003283143f},
+        { 0.2057106793f,  0.6331123710f},
+        {-0.8161166310f, -0.0000000002f},
+        { 0.2943962216f, -0.9060583711f},
+        { 0.8626962900f,  0.6267855763f},
+        {-0.9298345447f,  0.6755643487f},
+        {-0.3695388138f, -1.1373236179f},
+        { 1.2026044130f,  0.0000000000f},
+        {-0.3612760901f,  1.1118934155f},
+        {-0.8881026506f, -0.6452443004f},
+        { 0.8037882447f, -0.5839863420f},
+        { 0.2668806314f,  0.8213740587f},
+        {-0.7167864442f,  0.0000000002f},
+        { 0.1737996489f, -0.5349003077f},
+        { 0.3316683173f,  0.2409711480f},
+        {-0.2167777866f,  0.1574982852f},
+        {-0.0443248004f, -0.1364177018f},
+        { 0.0414781198f, -0.0000000000f},
+        { 0.0108570615f, -0.0334145986f},
+        { 0.0694981664f,  0.0504933707f},
+        {-0.0909759924f,  0.0660979301f},
+        {-0.0365069620f, -0.1123568788f},
+        { 0.1075555831f, -0.0000000001f},
+        {-0.0265575647f,  0.0817357823f},
+        {-0.0474071130f, -0.0344432816f},
+        { 0.0245569348f, -0.0178416576f},
+    },
+    {
+        { 0.0416239388f,  0.0302415621f},       /* Filter 11 */
+        {-0.0642707273f,  0.0466954149f},
+        {-0.0318213031f, -0.0979359001f},
+        { 0.1168202460f,  0.0000000001f},
+        {-0.0357431434f,  0.1100060865f},
+        {-0.0766197667f, -0.0556675196f},
+        { 0.0406288542f, -0.0295185894f},
+        {-0.0061465879f, -0.0189172514f},
+        { 0.1156847477f,  0.0000000001f},
+        {-0.0726031289f,  0.2234494686f},
+        {-0.3019059300f, -0.2193474919f},
+        { 0.4237467945f, -0.3078700602f},
+        { 0.2096759230f,  0.6453161240f},
+        {-0.8281568289f, -0.0000000002f},
+        { 0.2976299822f, -0.9160109162f},
+        { 0.8693157434f,  0.6315948367f},
+        {-0.9341484904f,  0.6786985993f},
+        {-0.3701899350f, -1.1393274069f},
+        { 1.2013363838f,  0.0000000000f},
+        {-0.3598691821f,  1.1075633764f},
+        {-0.8820219636f, -0.6408264637f},
+        { 0.7957338095f, -0.5781344771f},
+        { 0.2632595599f,  0.8102296591f},
+        {-0.7040774226f,  0.0000000002f},
+        { 0.1698123366f, -0.5226286054f},
+        { 0.3216831982f,  0.2337165177f},
+        {-0.2077746689f,  0.1509571373f},
+        {-0.0414163321f, -0.1274663657f},
+        { 0.0341038331f, -0.0000000000f},
+        { 0.0124666411f, -0.0383683741f},
+        { 0.0720072389f,  0.0523163229f},
+        {-0.0919580832f,  0.0668114573f},
+        {-0.0364049338f, -0.1120428666f},
+        { 0.1061014757f, -0.0000000001f},
+        {-0.0258991010f,  0.0797092393f},
+        {-0.0454812683f, -0.0330440775f},
+        { 0.0227260832f, -0.0165114664f},
+    },
+};
+
+static const uint8_t space_map_v22bis[6][6] =
+{
+    {11,  9,  9,  6,  6,  7},
+    {10,  8,  8,  4,  4,  5},
+    {10,  8,  8,  4,  4,  5},
+    {13, 12, 12,  0,  0,  2},
+    {13, 12, 12,  0,  0,  2},
+    {15, 14, 14,  1,  1,  3}
+};
+
+float v22bis_rx_carrier_frequency(v22bis_state_t *s)
+{
+    return dds_frequencyf(s->rx_carrier_phase_rate);
+}
+/*- End of function --------------------------------------------------------*/
+
+float v22bis_rx_symbol_timing_correction(v22bis_state_t *s)
+{
+    return (float) s->total_baud_timing_correction/((float) PULSESHAPER_COEFF_SETS*40.0f/(3.0f*2.0f));
+}
+/*- End of function --------------------------------------------------------*/
+
+float v22bis_rx_signal_power(v22bis_state_t *s)
+{
+    return power_meter_dbm0(&s->rx_power);
+}
+/*- End of function --------------------------------------------------------*/
+
+int v22bis_rx_equalizer_state(v22bis_state_t *s, complexf_t **coeffs)
+{
+    *coeffs = s->eq_coeff;
+    return 2*V22BIS_EQUALIZER_LEN + 1;
+}
+/*- End of function --------------------------------------------------------*/
+
+static void equalizer_reset(v22bis_state_t *s)
+{
+    int i;
+
+    /* Start with an equalizer based on everything being perfect */
+    for (i = 0;  i < 2*V22BIS_EQUALIZER_LEN + 1;  i++)
+        s->eq_coeff[i] = complex_setf(0.0f, 0.0f);
+    s->eq_coeff[V22BIS_EQUALIZER_LEN] = complex_setf(3.0f, 0.0f);
+    for (i = 0;  i <= V22BIS_EQUALIZER_MASK;  i++)
+        s->eq_buf[i] = complex_setf(0.0f, 0.0f);
+
+    s->eq_coeff[V22BIS_EQUALIZER_LEN - 6].re = -0.02f;
+    s->eq_coeff[V22BIS_EQUALIZER_LEN - 5].re =  0.035f;
+    s->eq_coeff[V22BIS_EQUALIZER_LEN - 4].re =  0.08f;
+    s->eq_coeff[V22BIS_EQUALIZER_LEN - 3].re = -0.30f;
+    s->eq_coeff[V22BIS_EQUALIZER_LEN - 2].re = -0.37f;
+    s->eq_coeff[V22BIS_EQUALIZER_LEN - 1].re =  0.09f;
+    s->eq_coeff[V22BIS_EQUALIZER_LEN].re     =  3.19f;
+    s->eq_coeff[V22BIS_EQUALIZER_LEN + 1].re =  0.09f;
+    s->eq_coeff[V22BIS_EQUALIZER_LEN + 2].re = -0.37f;
+    s->eq_coeff[V22BIS_EQUALIZER_LEN + 3].re = -0.30f;
+    s->eq_coeff[V22BIS_EQUALIZER_LEN + 5].re =  0.035f;
+    s->eq_coeff[V22BIS_EQUALIZER_LEN + 6].re = -0.02f;
+
+    s->eq_put_step = 20 - 1;
+    s->eq_step = 0;
+    s->eq_delta = EQUALIZER_DELTA/(2*V22BIS_EQUALIZER_LEN + 1);
+}
+/*- End of function --------------------------------------------------------*/
+
+static complexf_t equalizer_get(v22bis_state_t *s)
+{
+    int i;
+    int p;
+    complexf_t z;
+    complexf_t z1;
+
+    /* Get the next equalized value. */
+    z = complex_setf(0.0f, 0.0f);
+    p = s->eq_step - 1;
+    for (i = 0;  i < 2*V22BIS_EQUALIZER_LEN + 1;  i++)
+    {
+        p = (p - 1) & V22BIS_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(v22bis_state_t *s, const complexf_t *z, const complexf_t *target)
+{
+    int i;
+    int p;
+    complexf_t ez;
+    complexf_t z1;
+
+    /* Find the x and y mismatch from the exact constellation position. */
+    ez = complex_subf(target, z);
+    ez.re *= s->eq_delta;
+    ez.im *= s->eq_delta;
+
+    p = s->eq_step - 1;
+    for (i = 0;  i < 2*V22BIS_EQUALIZER_LEN + 1;  i++)
+    {
+        p = (p - 1) & V22BIS_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);
+        /* If we don't leak a little bit we seem to get some wandering adaption */
+        s->eq_coeff[i].re *= 0.9999f;
+        s->eq_coeff[i].im *= 0.9999f;
+    }
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ void track_carrier(v22bis_state_t *s, const complexf_t *z, const complexf_t *target)
+{
+    float error;
+
+    /* For small errors the imaginary part of the difference between the actual and the target
+       positions is proportional to the phase error, for any particular target. However, the
+       different amplitudes of the various target positions scale things. */
+    error = z->im*target->re - z->re*target->im;
+    
+    s->rx_carrier_phase_rate += (int32_t) (s->carrier_track_i*error);
+    s->rx_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__ void put_bit(v22bis_state_t *s, int bit)
+{
+    int out_bit;
+
+    bit &= 1;
+
+    /* Descramble the bit */
+    s->rx_scramble_reg = (s->rx_scramble_reg << 1) | bit;
+    out_bit = (bit ^ (s->rx_scramble_reg >> 15) ^ (s->rx_scramble_reg >> 18)) & 1;
+    if (s->rx_scrambler_pattern_count >= 64)
+    {
+        out_bit ^= 1;
+        s->rx_scrambler_pattern_count = 0;
+    }
+    if (bit)
+        s->rx_scrambler_pattern_count++;
+    else
+        s->rx_scrambler_pattern_count = 0;
+
+    s->put_bit(s->user_data, out_bit);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void decode_baud(v22bis_state_t *s, int nearest)
+{
+    static const uint8_t phase_steps[4] =
+    {
+        1, 0, 2, 3
+    };
+    int raw_bits;
+
+    raw_bits = phase_steps[((nearest - s->rx_constellation_state) >> 2) & 3];
+    /* The first two bits are the quadrant */
+    put_bit(s, raw_bits);
+    put_bit(s, raw_bits >> 1);
+    if (s->bit_rate == 2400)
+    {
+        /* The other two bits are the position within the quadrant */
+        put_bit(s, nearest >> 1);
+        put_bit(s, nearest);
+    }
+    s->rx_constellation_state = nearest;
+}
+/*- End of function --------------------------------------------------------*/
+
+static __inline__ int find_quadrant(const complexf_t *z)
+{
+    int b1;
+    int b2;
+
+#if 0
+    /* Split along the axes, as follows:
+        1  0
+        2  3
+     */
+    b1 = (z->re <= 0.0f);
+    b2 = (z->im <= 0.0f);
+    return (b2 << 1) | (b1 ^ b2);
+#endif    
+    /* Split the space along the two diagonals, as follows:
+         \ 1 /
+        2     0
+         / 3 \
+     */
+    b1 = (z->im > z->re);
+    b2 = (z->im < -z->re);
+    return (b2 << 1) | (b1 ^ b2);
+}
+/*- End of function --------------------------------------------------------*/
+
+static void process_half_baud(v22bis_state_t *s, const complexf_t *sample)
+{
+    complexf_t a;
+    complexf_t b;
+    complexf_t c;
+
+    complexf_t z;
+    complexf_t zz;
+    const complexf_t *target;
+    float p;
+    float q;
+    int re;
+    int im;
+    int nearest;
+
+    z.re = sample->re;
+    z.im = sample->im;
+
+    /* Add a sample to the equalizer's circular buffer, but don't calculate anything
+       at this time. */
+    s->eq_buf[s->eq_step] = z;
+    s->eq_step = (s->eq_step + 1) & V22BIS_EQUALIZER_MASK;
+
+    /* On alternate insertions we have a whole baud and must process it. */
+    if ((s->rx_baud_phase ^= 1))
+        return;
+
+    /* Perform a Gardner test for baud alignment on the three most recent samples. */
+#if 0
+    p = s->eq_buf[(s->eq_step - 3) & V22BIS_EQUALIZER_MASK].re
+      - s->eq_buf[(s->eq_step - 1) & V22BIS_EQUALIZER_MASK].re;
+    p *= s->eq_buf[(s->eq_step - 2) & V22BIS_EQUALIZER_MASK].re;
+
+    q = s->eq_buf[(s->eq_step - 3) & V22BIS_EQUALIZER_MASK].im
+      - s->eq_buf[(s->eq_step - 1) & V22BIS_EQUALIZER_MASK].im;
+    q *= s->eq_buf[(s->eq_step - 2) & V22BIS_EQUALIZER_MASK].im;
+#else
+    if (s->sixteen_way_decisions)
+    {
+        p = s->eq_buf[(s->eq_step - 3) & V22BIS_EQUALIZER_MASK].re
+          - s->eq_buf[(s->eq_step - 1) & V22BIS_EQUALIZER_MASK].re;
+        p *= s->eq_buf[(s->eq_step - 2) & V22BIS_EQUALIZER_MASK].re;
+
+        q = s->eq_buf[(s->eq_step - 3) & V22BIS_EQUALIZER_MASK].im
+        - s->eq_buf[(s->eq_step - 1) & V22BIS_EQUALIZER_MASK].im;
+        q *= s->eq_buf[(s->eq_step - 2) & V22BIS_EQUALIZER_MASK].im;
+    }
+    else
+    {
+        /* Rotate the points to the 45 degree positions, to maximise the effectiveness of the Gardner algorithm */
+        zz = complex_setf(cosf(26.57f*3.14159f/180.0f), sinf(26.57f*3.14159f/180.0f));
+        a = complex_mulf(&s->eq_buf[(s->eq_step - 3) & V22BIS_EQUALIZER_MASK], &zz);
+        b = complex_mulf(&s->eq_buf[(s->eq_step - 2) & V22BIS_EQUALIZER_MASK], &zz);
+        c = complex_mulf(&s->eq_buf[(s->eq_step - 1) & V22BIS_EQUALIZER_MASK], &zz);
+        p = (a.re - c.re)*b.re;
+        q = (a.im - c.im)*b.im;
+    }
+#endif
+
+    p += q;
+    s->gardner_integrate += ((p + q) > 0.0f)  ?  s->gardner_step  :  -s->gardner_step;
+
+    if (abs(s->gardner_integrate) >= 16)
+    {
+        /* This integrate and dump approach avoids rapid changes of the equalizer put step.
+           Rapid changes, without hysteresis, are bad. They degrade the equalizer performance
+           when the true symbol boundary is close to a sample boundary. */
+        s->eq_put_step += (s->gardner_integrate/16);
+        s->total_baud_timing_correction += (s->gardner_integrate/16);
+span_log(&s->logging, SPAN_LOG_FLOW, "Gardner kick %d [total %d]\n", s->gardner_integrate, s->total_baud_timing_correction);
+        if (s->qam_report)
+            s->qam_report(s->qam_user_data, NULL, NULL, s->gardner_integrate);
+        s->gardner_integrate = 0;
+    }
+
+    z = equalizer_get(s);
+printf("VVV %15.5f %15.5f\n", z.re, z.im);
+
+span_log(&s->logging, SPAN_LOG_FLOW, "VVV %p %d\n", s->user_data, s->rx_training);
+    if (s->sixteen_way_decisions)
+    {
+        re = (int) (z.re + 3.0f);
+        if (re > 5)
+            re = 5;
+        else if (re < 0)
+            re = 0;
+        im = (int) (z.im + 3.0f);
+        if (im > 5)
+            im = 5;
+        else if (im < 0)
+            im = 0;
+        nearest = space_map_v22bis[re][im];
+    }
+    else
+    {
+        zz = complex_setf(3.0f/sqrtf(10.0f), -1.0f/sqrtf(10.0f));
+        zz = complex_mulf(&z, &zz);
+        nearest = (find_quadrant(&zz) << 2) | 0x01;
+        printf("Trackit %15.5f %15.5f     %15.5f %15.5f   %d\n", z.re, z.im, zz.re, zz.im, nearest);
+    }
+
+    switch (s->rx_training)
+    {
+    case V22BIS_TRAINING_STAGE_NORMAL_OPERATION:
+        /* Normal operation. */
+        track_carrier(s, &z, &v22bis_constellation[nearest]);
+        tune_equalizer(s, &z, &v22bis_constellation[nearest]);
+        decode_baud(s, nearest);
+        target = &v22bis_constellation[s->rx_constellation_state];
+        break;
+    case V22BIS_TRAINING_STAGE_SYMBOL_ACQUISITION:
+        /* Allow time for the Gardner algorithm to settle the symbol timing. */
+        target = &z;
+        if (++s->rx_training_count >= 40)
+        {
+            s->gardner_step = 4;
+            if (s->caller)
+            {
+                s->rx_training = V22BIS_TRAINING_STAGE_UNSCRAMBLED_ONES;
+            }
+            else
+            {
+                if (s->bit_rate == 2400)
+                    s->rx_training = V22BIS_TRAINING_STAGE_UNSCRAMBLED_0011;
+                else
+                    s->rx_training = V22BIS_TRAINING_STAGE_SCRAMBLED_ONES_AT_1200;
+            }
+            break;
+        }
+
+        /* QAM and Gardner only play nicely with heavy damping, so we need to change to
+           a slow rate of symbol timing adaption. However, it must not be so slow that it
+           cannot track the worst case timing error specified in V.22bis. This should be 0.01%,
+           but since we might be off in the opposite direction from the source, the total
+           error could be higher. */
+        if (s->rx_training_count == 30)
+            s->gardner_step = 32;
+        break;
+    case V22BIS_TRAINING_STAGE_UNSCRAMBLED_ONES:
+        /* The answering modem should initially receive unscrambled ones at 1200bps */
+        track_carrier(s, &z, &v22bis_constellation[nearest]);
+        target = &z;
+        if (nearest == ((s->rx_constellation_state - 4) & 0x0F))
+            s->detected_unscrambled_ones++;
+        if (nearest == ((s->rx_constellation_state + 4) & 0x0F))
+            s->detected_unscrambled_zeros++;
+        s->rx_constellation_state = nearest;
+span_log(&s->logging, SPAN_LOG_FLOW, "TWIDDLING THUMBS - %d\n", s->rx_training_count);
+        if (++s->rx_training_count == ms_to_symbols(155 + 456))
+        {
+            if (s->detected_unscrambled_ones >= 250  ||  s->detected_unscrambled_zeros >= 250)
+                s->detected_unscrambled_ones_or_zeros = TRUE;
+        }
+        if (s->rx_training_count == ms_to_symbols(155 + 457))
+        {
+            /* We should only bother looking for the 2400bps marker if we are allowed to use
+               2400bps */
+            s->rx_training_count = 0;
+            if (s->bit_rate == 2400)
+                s->rx_training = V22BIS_TRAINING_STAGE_UNSCRAMBLED_0011;
+            else
+                s->rx_training = V22BIS_TRAINING_STAGE_SCRAMBLED_ONES_AT_1200;
+        }
+        break;
+    case V22BIS_TRAINING_STAGE_UNSCRAMBLED_0011:
+s->sixteen_way_decisions = TRUE;
+        /* If we can actually find this it means we can use 2400bps. If we find unscrambled ones, it means we
+           we must use 1200bps. */
+        track_carrier(s, &z, &v22bis_constellation[nearest]);
+        tune_equalizer(s, &z, &v22bis_constellation[nearest]);
+        target = &z;
+        //s->carrier_track_i = 1000.0f;
+        //s->carrier_track_p = 1000000.0f;
+#if 0
+        if (++s->rx_training_count > ms_to_symbols(800))
+        {
+            s->detected_unscrambled_0011_ending = TRUE;
+            s->rx_training = V22BIS_TRAINING_STAGE_UNSCRAMBLED_ONES;
+        }
+#else
+        if (++s->rx_training_count == 1)
+        {
+            s->detected_unscrambled_zeros = nearest;
+            s->detected_unscrambled_ones = 0;
+        }
+        else
+        {
+span_log(&s->logging, SPAN_LOG_FLOW, "0x%X 0x%X 0x%X\n", s->detected_unscrambled_zeros, nearest, (s->detected_unscrambled_zeros + nearest) & 0x0F);
+            if ((s->rx_training_count & 1) == 0)
+            {
+span_log(&s->logging, SPAN_LOG_FLOW, "AAA\n");
+                if (((s->detected_unscrambled_zeros + nearest) & 0x0F) == 0x06)
+                    s->detected_unscrambled_ones = 1;
+                else if (((s->detected_unscrambled_zeros + nearest) & 0x0F) == 0x02)
+                    s->detected_unscrambled_ones = -1;
+                else
+                {
+span_log(&s->logging, SPAN_LOG_FLOW, "AAA 1\n");
+                    if (s->detected_unscrambled_ones > 5  ||  s->detected_unscrambled_ones < -5)
+                        s->detected_unscrambled_0011_ending = TRUE;
+                    else
+                        s->bit_rate = 1200;
+                    s->rx_training = V22BIS_TRAINING_STAGE_SCRAMBLED_ONES_AT_1200;
+                }
+            }
+            else
+            {
+span_log(&s->logging, SPAN_LOG_FLOW, "BBB\n");
+                if (((s->detected_unscrambled_zeros + nearest) & 0x0F) == 0x06)
+                    s->detected_unscrambled_ones = 1;
+                else if (((s->detected_unscrambled_zeros + nearest) & 0x0F) == 0x02)
+                    s->detected_unscrambled_ones = -1;
+                else
+                {
+span_log(&s->logging, SPAN_LOG_FLOW, "BBB 1\n");
+                    if (s->detected_unscrambled_ones > 5  ||  s->detected_unscrambled_ones < -5)
+                        s->detected_unscrambled_0011_ending = TRUE;
+                    else
+                        s->bit_rate = 1200;
+                    s->rx_training = V22BIS_TRAINING_STAGE_SCRAMBLED_ONES_AT_1200;
+                }
+            }
+        }
+#endif
+        break;
+    case V22BIS_TRAINING_STAGE_SCRAMBLED_ONES_AT_1200:
+        track_carrier(s, &z, &v22bis_constellation[nearest]);
+        tune_equalizer(s, &z, &v22bis_constellation[nearest]);
+        target = &z;
+span_log(&s->logging, SPAN_LOG_FLOW, "S11 0x%02x\n", nearest);
+        if (++s->rx_training_count > ms_to_symbols(900))
+        {
+            s->detected_scrambled_ones_or_zeros_at_1200bps = TRUE;
+            s->rx_training = V22BIS_TRAINING_STAGE_NORMAL_OPERATION;
+        }
+        if (s->bit_rate == 2400  &&  s->rx_training_count == ms_to_symbols(450))
+            s->sixteen_way_decisions = TRUE;
+        break;
+    case V22BIS_TRAINING_STAGE_SCRAMBLED_ONES_AT_2400:
+        track_carrier(s, &z, &v22bis_constellation[nearest]);
+        tune_equalizer(s, &z, &v22bis_constellation[nearest]);
+        s->sixteen_way_decisions = TRUE;
+        target = &z;
+        break;
+    case V22BIS_TRAINING_STAGE_PARKED:
+    default:
+        /* We failed to train! */
+        /* Park here until the carrier drops. */
+        target = &z;
+        break;
+    }
+    if (s->qam_report)
+        s->qam_report(s->qam_user_data, &z, target, s->rx_constellation_state);
+}
+/*- End of function --------------------------------------------------------*/
+
+int v22bis_rx(v22bis_state_t *s, const int16_t amp[], int len)
+{
+    int i;
+    int j;
+    int step;
+    complexf_t z;
+    complexf_t zz;
+    int32_t power;
+    complexf_t sample;
+    float ii;
+    float qq;
+
+    for (i = 0;  i < len;  i++)
+    {
+        /* Complex bandpass filter the signal, using a pair of FIRs, and RRC coeffs shifted
+           to centre at 1200Hz or 2400Hz. The filters support 12 fractional phase shifts, to 
+           permit signal extraction very close to the middle of a symbol. */
+        s->rx_rrc_filter[s->rx_rrc_filter_step] =
+        s->rx_rrc_filter[s->rx_rrc_filter_step + V22BIS_RX_FILTER_STEPS] = amp[i];
+        if (++s->rx_rrc_filter_step >= V22BIS_RX_FILTER_STEPS)
+            s->rx_rrc_filter_step = 0;
+
+        /* Calculate the I filter, with an arbitrary phase step, just so we can calculate
+           the signal power. */
+        /* TODO: get rid of this */
+        if (s->caller)
+        {
+            ii = pulseshaper_2400[6][0].re*s->rx_rrc_filter[s->rx_rrc_filter_step];
+            for (j = 1;  j < V22BIS_RX_FILTER_STEPS;  j++)
+                ii += pulseshaper_2400[6][j].re*s->rx_rrc_filter[j + s->rx_rrc_filter_step];
+        }
+        else
+        {
+            ii = pulseshaper_1200[6][0].re*s->rx_rrc_filter[s->rx_rrc_filter_step];
+            for (j = 1;  j < V22BIS_RX_FILTER_STEPS;  j++)
+                ii += pulseshaper_1200[6][j].re*s->rx_rrc_filter[j + s->rx_rrc_filter_step];
+        }
+        power = power_meter_update(&(s->rx_power), (int16_t) (ii/10.0f));
+        if (s->carrier_present)
+        {
+            /* Look for power below -48dBm0 to turn the carrier off */
+            if (power < s->carrier_off_power)
+            {
+                v22bis_rx_restart(s, s->bit_rate);
+                s->put_bit(s->user_data, PUTBIT_CARRIER_DOWN);
+                continue;
+            }
+        }
+        else
+        {
+            /* Look for power exceeding -43dBm0 to turn the carrier on */
+            if (power < s->carrier_on_power)
+                continue;
+            s->carrier_present = TRUE;
+            s->put_bit(s->user_data, PUTBIT_CARRIER_UP);
+        }
+        if (s->rx_training != V22BIS_TRAINING_STAGE_PARKED)
+        {
+            /* Only spend effort processing this data if the modem is not
+               parked, after training failure. */
+            z = dds_complexf(&(s->rx_carrier_phase), s->rx_carrier_phase_rate);
+            if (s->rx_training == V22BIS_TRAINING_STAGE_SYMBOL_ACQUISITION)
+            {
+                /* Only AGC during the initial symbol acquisition, and then lock the gain. */
+                s->agc_scaling = 0.018f*3.60f/sqrtf(power);
+            }
+            /* Put things into the equalization buffer at T/2 rate. The Gardner algorithm
+               will fiddle the step to align this with the symbols. */
+            if ((s->eq_put_step -= PULSESHAPER_COEFF_SETS) <= 0)
+            {
+                /* Pulse shape while still at the carrier frequency, using a quadrature
+                   pair of filters. This results in a properly bandpass filtered complex
+                   signal, which can be brought directly to bandband by complex mixing.
+                   No further filtering, to remove mixer harmonics, is needed. */
+                step = -s->eq_put_step;
+                if (step > PULSESHAPER_COEFF_SETS - 1)
+                    step = PULSESHAPER_COEFF_SETS - 1;
+                s->eq_put_step += PULSESHAPER_COEFF_SETS*40/(3*2);
+                if (s->caller)
+                {
+                    ii = pulseshaper_2400[step][0].re*s->rx_rrc_filter[s->rx_rrc_filter_step];
+                    qq = pulseshaper_2400[step][0].im*s->rx_rrc_filter[s->rx_rrc_filter_step];
+                    for (j = 1;  j < V22BIS_RX_FILTER_STEPS;  j++)
+                    {
+                        ii += pulseshaper_2400[step][j].re*s->rx_rrc_filter[j + s->rx_rrc_filter_step];
+                        qq += pulseshaper_2400[step][j].im*s->rx_rrc_filter[j + s->rx_rrc_filter_step];
+                    }
+                }
+                else
+                {
+                    ii = pulseshaper_1200[step][0].re*s->rx_rrc_filter[s->rx_rrc_filter_step];
+                    qq = pulseshaper_1200[step][0].im*s->rx_rrc_filter[s->rx_rrc_filter_step];
+                    for (j = 1;  j < V22BIS_RX_FILTER_STEPS;  j++)
+                    {
+                        ii += pulseshaper_1200[step][j].re*s->rx_rrc_filter[j + s->rx_rrc_filter_step];
+                        qq += pulseshaper_1200[step][j].im*s->rx_rrc_filter[j + s->rx_rrc_filter_step];
+                    }
+                }
+                sample.re = ii*s->agc_scaling;
+                sample.im = qq*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 = sample.re*z.re - sample.im*z.im;
+                zz.im = -sample.re*z.im - sample.im*z.re;
+                process_half_baud(s, &zz);
+            }
+        }
+    }
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+int v22bis_rx_restart(v22bis_state_t *s, int bit_rate)
+{
+    /* If bit_rate is 2400, the real bit rate is negotiated. If bit_rate
+       is 1200, the real bit rate is forced to 1200. */
+    s->bit_rate = bit_rate;
+    vec_zerof(s->rx_rrc_filter, sizeof(s->rx_rrc_filter)/sizeof(s->rx_rrc_filter[0]));
+    s->rx_rrc_filter_step = 0;
+    s->rx_scramble_reg = 0;
+    s->rx_scrambler_pattern_count = 0;
+    s->rx_training = V22BIS_TRAINING_STAGE_SYMBOL_ACQUISITION;
+    s->rx_training_count = 0;
+    s->carrier_present = FALSE;
+
+    s->rx_carrier_phase_rate = dds_phase_ratef((s->caller)  ?  2400.0f  :  1200.0f);
+    s->rx_carrier_phase = 0;
+    power_meter_init(&(s->rx_power), 5);
+    s->carrier_on_power = power_meter_level_dbm0(-43);
+    s->carrier_off_power = power_meter_level_dbm0(-48);
+    s->agc_scaling = 0.0005f*0.025f;
+
+    s->rx_constellation_state = 0;
+    s->sixteen_way_decisions = FALSE;
+
+    equalizer_reset(s);
+
+    s->detected_unscrambled_ones = 0;
+    s->detected_unscrambled_zeros = 0;
+    s->gardner_integrate = 0;
+    s->gardner_step = 256;
+    s->rx_baud_phase = 0;
+    s->carrier_track_i = 8000.0f;
+    s->carrier_track_p = 8000000.0f;
+    return 0;
+}
+/*- End of function --------------------------------------------------------*/
+
+void v22bis_rx_set_qam_report_handler(v22bis_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.