//
//    This file is part of Dire Wolf, an amateur radio packet TNC.
// 
//    Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2019  John Langner, WB2OSZ
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 2 of the License, or
//    (at your option) any later version.
//
//    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, see <http://www.gnu.org/licenses/>.
//



/*------------------------------------------------------------------
 *
 * Module:      demod.c
 *
 * Purpose:   	Common entry point for multiple types of demodulators.
 *		
 * Input:	Audio samples from either a file or the "sound card."
 *
 * Outputs:	Calls hdlc_rec_bit() for each bit demodulated.  
 *
 *---------------------------------------------------------------*/

#include "direwolf.h"

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>

#include "audio.h"
#include "demod.h"
#include "tune.h"
#include "fsk_demod_state.h"
#include "fsk_gen_filter.h"
#include "hdlc_rec.h"
#include "textcolor.h"
#include "demod_9600.h"
#include "demod_afsk.h"
#include "demod_psk.h"



// Properties of the radio channels.

static struct audio_s          *save_audio_config_p;


// TODO: temp experiment.


static int zerostuff = 1;	// temp experiment.

// Current state of all the decoders.

static struct demodulator_state_s demodulator_state[MAX_CHANS][MAX_SUBCHANS];


static int sample_sum[MAX_CHANS][MAX_SUBCHANS];
static int sample_count[MAX_CHANS][MAX_SUBCHANS];


/*------------------------------------------------------------------
 *
 * Name:        demod_init
 *
 * Purpose:     Initialize the demodulator(s) used for reception.
 *
 * Inputs:      pa		- Pointer to audio_s structure with
 *				  various parameters for the modem(s).
 *
 * Returns:     0 for success, -1 for failure.
 *		
 *
 * Bugs:	This doesn't do much error checking so don't give it
 *		anything crazy.
 *
 *----------------------------------------------------------------*/

int demod_init (struct audio_s *pa)
{
	int chan;		/* Loop index over number of radio channels. */
	char profile;
	


/*
 * Save audio configuration for later use.
 */

	save_audio_config_p = pa;

	for (chan = 0; chan < MAX_CHANS; chan++) {

	 if (save_audio_config_p->achan[chan].medium == MEDIUM_RADIO) {

	  char *p;
	  char just_letters[16];
	  int num_letters;
	  int have_plus;

	  /*
	   * These are derived from config file parameters.
	   *
	   * num_subchan is number of demodulators.
	   * This can be increased by:
	   *	Multiple frequencies.
	   *	Multiple letters (not sure if I will continue this).
	   *
	   * num_slicers is set to max by the "+" option.
	   */

	  save_audio_config_p->achan[chan].num_subchan = 1;
	  save_audio_config_p->achan[chan].num_slicers = 1;

	  switch (save_audio_config_p->achan[chan].modem_type) {

	    case MODEM_OFF:
	      break;

	    case MODEM_AFSK:
	    case MODEM_EAS:

	      if (save_audio_config_p->achan[chan].modem_type == MODEM_EAS) {
		if (save_audio_config_p->achan[chan].fix_bits != RETRY_NONE) {
	          text_color_set(DW_COLOR_INFO);
		  dw_printf ("Channel %d: FIX_BITS option has been turned off for EAS.\n", chan);
	          save_audio_config_p->achan[chan].fix_bits = RETRY_NONE;
	        }
		if (save_audio_config_p->achan[chan].passall != 0) {
	          text_color_set(DW_COLOR_INFO);
		  dw_printf ("Channel %d: PASSALL option has been turned off for EAS.\n", chan);
	          save_audio_config_p->achan[chan].passall = 0;
	        }
	      }

/*
 * Tear apart the profile and put it back together in a normalized form:
 *	- At least one letter, supply suitable default if necessary.
 *	- Upper case only.
 *	- Any plus will be at the end.
 */
	      num_letters = 0;
	      just_letters[num_letters] = '\0';
	      have_plus = 0;
	      for (p = save_audio_config_p->achan[chan].profiles; *p != '\0'; p++) {

	        if (islower(*p)) {
	          just_letters[num_letters] = toupper(*p);
	          num_letters++;
	          just_letters[num_letters] = '\0';
	        }

	        else if (isupper(*p)) {
	          just_letters[num_letters] = *p;
	          num_letters++;
	          just_letters[num_letters] = '\0';
	        }

	        else if (*p == '+') {
	          have_plus = 1;
	          if (p[1] != '\0') {
		    text_color_set(DW_COLOR_ERROR);
		    dw_printf ("Channel %d: + option must appear at end of demodulator types \"%s\" \n", 
					chan, save_audio_config_p->achan[chan].profiles);
		  }	    
	        }

	        else if (*p == '-') {
	          have_plus = -1;
	          if (p[1] != '\0') {
		    text_color_set(DW_COLOR_ERROR);
		    dw_printf ("Channel %d: - option must appear at end of demodulator types \"%s\" \n", 
					chan, save_audio_config_p->achan[chan].profiles);
		  }	
    
	        } else {
		  text_color_set(DW_COLOR_ERROR);
		  dw_printf ("Channel %d: Demodulator types \"%s\" can contain only letters and + - characters.\n", 
					chan, save_audio_config_p->achan[chan].profiles);
	        }
	      }

	      assert (num_letters == (int)(strlen(just_letters)));

/*
 * Pick a good default demodulator if none specified. 
 */
	      if (num_letters == 0) {

	        if (save_audio_config_p->achan[chan].baud < 600) {

	          /* This has been optimized for 300 baud. */

	          strlcpy (just_letters, "D", sizeof(just_letters));

	        }
	        else {
#if __arm__
	          /* We probably don't have a lot of CPU power available. */
	          /* Previously we would use F if possible otherwise fall back to A. */

	          /* In version 1.2, new default is E+ /3. */
	          strlcpy (just_letters, "E", sizeof(just_letters));			// version 1.2 now E.
	          if (have_plus != -1) have_plus = 1;		// Add as default for version 1.2
								// If not explicitly turned off.
	          if (save_audio_config_p->achan[chan].decimate == 0) {
	            if (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) {
	              save_audio_config_p->achan[chan].decimate = 3;
	            }
	          }
#else
	          strlcpy (just_letters, "E", sizeof(just_letters));			// version 1.2 changed C to E.
	          if (have_plus != -1) have_plus = 1;		// Add as default for version 1.2
								// If not explicitly turned off.
#endif
	        }
	        num_letters = 1;
	      }


	      assert (num_letters == (int)(strlen(just_letters)));

/*
 * Put it back together again.
 */

		/* At this point, have_plus can have 3 values: */
		/* 	1 = turned on, either explicitly or by applied default */
		/*	-1 = explicitly turned off.  change to 0 here so it is false. */
		/* 	0 = off by default. */

	      if (have_plus == -1) have_plus = 0;

	      strlcpy (save_audio_config_p->achan[chan].profiles, just_letters, sizeof(save_audio_config_p->achan[chan].profiles));
	      
	      assert (strlen(save_audio_config_p->achan[chan].profiles) >= 1);

	      if (have_plus) {
	        strlcat (save_audio_config_p->achan[chan].profiles, "+", sizeof(save_audio_config_p->achan[chan].profiles));
	      }

	      /* These can be increased later for the multi-frequency case. */

	      save_audio_config_p->achan[chan].num_subchan = num_letters;
	      save_audio_config_p->achan[chan].num_slicers = 1;

/*
 * Some error checking - Can use only one of these:
 *
 *	- Multiple letters.
 *	- New + multi-slicer.
 *	- Multiple frequencies.
 */

	      if (have_plus && save_audio_config_p->achan[chan].num_freq > 1) {

		  text_color_set(DW_COLOR_ERROR);
		  dw_printf ("Channel %d: Demodulator + option can't be combined with multiple frequencies.\n", chan);
	          save_audio_config_p->achan[chan].num_subchan = 1;	// Will be set higher later.
	          save_audio_config_p->achan[chan].num_freq = 1;
	      }

	      if (num_letters > 1 && save_audio_config_p->achan[chan].num_freq > 1) {

		  text_color_set(DW_COLOR_ERROR);
		  dw_printf ("Channel %d: Multiple demodulator types can't be combined with multiple frequencies.\n", chan);

	          save_audio_config_p->achan[chan].profiles[1] = '\0';
		  num_letters = 1;
	      }

	      if (save_audio_config_p->achan[chan].decimate == 0) {
	        save_audio_config_p->achan[chan].decimate = 1;
		if (strchr (just_letters, 'D') != NULL && save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec > 40000) {
		  save_audio_config_p->achan[chan].decimate = 3;
		}
	      }

	      text_color_set(DW_COLOR_DEBUG);
	      dw_printf ("Channel %d: %d baud, AFSK %d & %d Hz, %s, %d sample rate",
		    chan, save_audio_config_p->achan[chan].baud, 
		    save_audio_config_p->achan[chan].mark_freq, save_audio_config_p->achan[chan].space_freq,
		    save_audio_config_p->achan[chan].profiles,
		    save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec);
	      if (save_audio_config_p->achan[chan].decimate != 1) 
	        dw_printf (" / %d", save_audio_config_p->achan[chan].decimate);
	      if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) 
	        dw_printf (", DTMF decoder enabled");
	      dw_printf (".\n");


/* 
 * Initialize the demodulator(s).
 *
 * We have 3 cases to consider.
 */

// TODO1.3: revisit this logic now that it is less restrictive.

	      if (num_letters > 1) {
	        int d;

/*	
 * Multiple letters, usually for 1200 baud.
 * Each one corresponds to a demodulator and subchannel.
 *
 * An interesting experiment but probably not too useful.
 * Can't have multiple frequency pairs.
 * In version 1.3 this can be combined with the + option.
 */

	        save_audio_config_p->achan[chan].num_subchan = num_letters;
		
		if (save_audio_config_p->achan[chan].num_subchan != num_letters) {
		  text_color_set(DW_COLOR_ERROR);
		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_subchan(%d) != strlen(\"%s\")\n",
				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_subchan, save_audio_config_p->achan[chan].profiles);
		}

	        if (save_audio_config_p->achan[chan].num_freq != 1) {
		  text_color_set(DW_COLOR_ERROR);
		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != 1\n",
				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq);
		}

	        for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) {
	          int mark, space;
	          assert (d >= 0 && d < MAX_SUBCHANS);

	          struct demodulator_state_s *D;
	          D = &demodulator_state[chan][d];

	          profile = save_audio_config_p->achan[chan].profiles[d];
	          mark = save_audio_config_p->achan[chan].mark_freq;
	          space = save_audio_config_p->achan[chan].space_freq;

	          if (save_audio_config_p->achan[chan].num_subchan != 1) {
	            text_color_set(DW_COLOR_DEBUG);
	            dw_printf ("        %d.%d: %c %d & %d\n", chan, d, profile, mark, space);
	          }

	          demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, 
			    save_audio_config_p->achan[chan].baud,
		            mark, 
	                    space,
			    profile,
			    D);

	          if (have_plus) {
		    /* I'm not happy about putting this hack here. */
		    /* should pass in as a parameter rather than adding on later. */

	            save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
		    D->num_slicers = MAX_SLICERS;
	          }

	          /* For signal level reporting, we want a longer term view. */
		  // TODO: Should probably move this into the init functions.

	          D->quick_attack = D->agc_fast_attack * 0.2f;
	          D->sluggish_decay = D->agc_slow_decay * 0.2f;
	        }
	      }
	      else if (have_plus) {
	       
/*
 * PLUS - which (formerly) implies we have only one letter and one frequency pair.
 *
 * One demodulator feeds multiple slicers, each a subchannel.
 */

	        if (num_letters != 1) {
		  text_color_set(DW_COLOR_ERROR);
		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, strlen(\"%s\") != 1\n",
				__FILE__, __LINE__, chan, just_letters);
		}

	        if (save_audio_config_p->achan[chan].num_freq != 1) {
		  text_color_set(DW_COLOR_ERROR);
		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != 1\n",
				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq);
		}

	        if (save_audio_config_p->achan[chan].num_freq != save_audio_config_p->achan[chan].num_subchan) {
		  text_color_set(DW_COLOR_ERROR);
		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, num_freq(%d) != num_subchan(%d)\n",
				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].num_freq, save_audio_config_p->achan[chan].num_subchan);
		}

	        struct demodulator_state_s *D;
	        D = &demodulator_state[chan][0];

		/* I'm not happy about putting this hack here. */
		/* This belongs in demod_afsk_init but it doesn't have access to the audio config. */

	        save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
     
	        demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, 
			save_audio_config_p->achan[chan].baud,
			save_audio_config_p->achan[chan].mark_freq, 
	                save_audio_config_p->achan[chan].space_freq,
			save_audio_config_p->achan[chan].profiles[0],
			D);

	        if (have_plus) {
		  /* I'm not happy about putting this hack here. */
		  /* should pass in as a parameter rather than adding on later. */

	          save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
		  D->num_slicers = MAX_SLICERS;
	        }

	        /* For signal level reporting, we want a longer term view. */

	        D->quick_attack = D->agc_fast_attack * 0.2f;
	        D->sluggish_decay = D->agc_slow_decay * 0.2f;
	      }	
	      else {
	        int d;
/*
 * One letter.
 * Can be combined with multiple frequencies.
 */

	        if (num_letters != 1) {
		  text_color_set(DW_COLOR_ERROR);
		  dw_printf ("INTERNAL ERROR, %s:%d, chan=%d, strlen(\"%s\") != 1\n",
				__FILE__, __LINE__, chan, save_audio_config_p->achan[chan].profiles);
		}

	        save_audio_config_p->achan[chan].num_subchan = save_audio_config_p->achan[chan].num_freq;

	        for (d = 0; d < save_audio_config_p->achan[chan].num_freq; d++) {

	          int mark, space, k;
	          assert (d >= 0 && d < MAX_SUBCHANS);

	          struct demodulator_state_s *D;
	          D = &demodulator_state[chan][d];

	          profile = save_audio_config_p->achan[chan].profiles[0];

	          k = d * save_audio_config_p->achan[chan].offset - ((save_audio_config_p->achan[chan].num_freq - 1) * save_audio_config_p->achan[chan].offset) / 2;
	          mark = save_audio_config_p->achan[chan].mark_freq + k;
	          space = save_audio_config_p->achan[chan].space_freq + k;

	          if (save_audio_config_p->achan[chan].num_freq != 1) {
	            text_color_set(DW_COLOR_DEBUG);
	            dw_printf ("        %d.%d: %c %d & %d\n", chan, d, profile, mark, space);
	          }
      
	          demod_afsk_init (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, 
			save_audio_config_p->achan[chan].baud,
			mark, space,
			profile,
			D);

	          if (have_plus) {
		    /* I'm not happy about putting this hack here. */
		    /* should pass in as a parameter rather than adding on later. */

	            save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
		    D->num_slicers = MAX_SLICERS;
	          }

	          /* For signal level reporting, we want a longer term view. */

	          D->quick_attack = D->agc_fast_attack * 0.2f;
	          D->sluggish_decay = D->agc_slow_decay * 0.2f;

	        } 	  /* for each freq pair */
	      }	
	      break;

	    case MODEM_QPSK:		// New for 1.4

	      // In versions 1.4 and 1.5, V.26 "Alternative A" was used.
	      // years later, I discover that the MFJ-2400 used "Alternative B."
	      // It looks like the other two manufacturers use the same but we
              // can't be sure until we find one for compatbility testing.

	      // In version 1.6 we add a choice for the user.
	      // If neither one was explicitly specified, print a message and take
	      // a default.  My current thinking is that we default to direwolf <= 1.5
	      // compatible for version 1.6 and MFJ compatible after that.

	      if (save_audio_config_p->achan[chan].v26_alternative == V26_UNSPECIFIED) {

	        text_color_set(DW_COLOR_ERROR);
	        dw_printf ("Two incompatible versions of 2400 bps QPSK are now available.\n");
	        dw_printf ("For compatbility with direwolf <= 1.5, use 'V26A' modem option in config file.\n");
	        dw_printf ("For compatbility MFJ-2400 use 'V26B' modem option in config file.\n");
	        dw_printf ("Command line options -j and -J can be used for channel 0.\n");
	        dw_printf ("For more information, read the Dire Wolf User Guide and\n");
	        dw_printf ("2400-4800-PSK-for-APRS-Packet-Radio.pdf.\n");
	        dw_printf ("The default is now MFJ-2400 compatibility mode.\n");

	        save_audio_config_p->achan[chan].v26_alternative = V26_DEFAULT;
	      }


// TODO: See how much CPU this takes on ARM and decide if we should have different defaults.

	      if (strlen(save_audio_config_p->achan[chan].profiles) == 0) {
//#if __arm__
//	        strlcpy (save_audio_config_p->achan[chan].profiles, "R", sizeof(save_audio_config_p->achan[chan].profiles));
//#else
	        strlcpy (save_audio_config_p->achan[chan].profiles, "PQRS", sizeof(save_audio_config_p->achan[chan].profiles));
//#endif
	      }
	      save_audio_config_p->achan[chan].num_subchan = strlen(save_audio_config_p->achan[chan].profiles);

	      save_audio_config_p->achan[chan].decimate = 1;	// think about this later.
	      text_color_set(DW_COLOR_DEBUG);
	      dw_printf ("Channel %d: %d bps, QPSK, %s, %d sample rate",
		    chan, save_audio_config_p->achan[chan].baud,
		    save_audio_config_p->achan[chan].profiles,
		    save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec);
	      if (save_audio_config_p->achan[chan].decimate != 1)
	        dw_printf (" / %d", save_audio_config_p->achan[chan].decimate);

	      if (save_audio_config_p->achan[chan].v26_alternative == V26_B)
	        dw_printf (", compatible with MFJ-2400");
	      else
	        dw_printf (", compatible with earlier direwolf");

	      if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF)
	        dw_printf (", DTMF decoder enabled");
	      dw_printf (".\n");

	      int d;
	      for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) {

	        assert (d >= 0 && d < MAX_SUBCHANS);
	        struct demodulator_state_s *D;
	        D = &demodulator_state[chan][d];
	        profile = save_audio_config_p->achan[chan].profiles[d];

	        //text_color_set(DW_COLOR_DEBUG);
	        //dw_printf ("About to call demod_psk_init for Q-PSK case, modem_type=%d, profile='%c'\n",
		//	save_audio_config_p->achan[chan].modem_type, profile);

	        demod_psk_init (save_audio_config_p->achan[chan].modem_type,
			save_audio_config_p->achan[chan].v26_alternative,
			save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate, 
			save_audio_config_p->achan[chan].baud,
			profile,
			D);

	        //text_color_set(DW_COLOR_DEBUG);
	        //dw_printf ("Returned from demod_psk_init\n");

	        /* For signal level reporting, we want a longer term view. */
		/* Guesses based on 9600.  Maybe revisit someday. */

	        D->quick_attack = 0.080 * 0.2;
	        D->sluggish_decay = 0.00012 * 0.2;
	      }
	      break;

	    case MODEM_8PSK:		// New for 1.4

// TODO: See how much CPU this takes on ARM and decide if we should have different defaults.

	      if (strlen(save_audio_config_p->achan[chan].profiles) == 0) {
//#if __arm__
//	        strlcpy (save_audio_config_p->achan[chan].profiles, "V", sizeof(save_audio_config_p->achan[chan].profiles));
//#else
	        strlcpy (save_audio_config_p->achan[chan].profiles, "TUVW", sizeof(save_audio_config_p->achan[chan].profiles));
//#endif
	      }
	      save_audio_config_p->achan[chan].num_subchan = strlen(save_audio_config_p->achan[chan].profiles);

	      save_audio_config_p->achan[chan].decimate = 1;	// think about this later
	      text_color_set(DW_COLOR_DEBUG);
	      dw_printf ("Channel %d: %d bps, 8PSK, %s, %d sample rate",
		    chan, save_audio_config_p->achan[chan].baud,
		    save_audio_config_p->achan[chan].profiles,
		    save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec);
	      if (save_audio_config_p->achan[chan].decimate != 1)
	        dw_printf (" / %d", save_audio_config_p->achan[chan].decimate);
	      if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF)
	        dw_printf (", DTMF decoder enabled");
	      dw_printf (".\n");

	      //int d;
	      for (d = 0; d < save_audio_config_p->achan[chan].num_subchan; d++) {

	        assert (d >= 0 && d < MAX_SUBCHANS);
	        struct demodulator_state_s *D;
	        D = &demodulator_state[chan][d];
	        profile = save_audio_config_p->achan[chan].profiles[d];

	        //text_color_set(DW_COLOR_DEBUG);
	        //dw_printf ("About to call demod_psk_init for 8-PSK case, modem_type=%d, profile='%c'\n",
		//	save_audio_config_p->achan[chan].modem_type, profile);

	        demod_psk_init (save_audio_config_p->achan[chan].modem_type,
			save_audio_config_p->achan[chan].v26_alternative,
			save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec / save_audio_config_p->achan[chan].decimate,
			save_audio_config_p->achan[chan].baud,
			profile,
			D);

	        //text_color_set(DW_COLOR_DEBUG);
	        //dw_printf ("Returned from demod_psk_init\n");

	        /* For signal level reporting, we want a longer term view. */
		/* Guesses based on 9600.  Maybe revisit someday. */

	        D->quick_attack = 0.080 * 0.2;
	        D->sluggish_decay = 0.00012 * 0.2;
	      }
	      break;

//TODO: how about MODEM_OFF case?

	    case MODEM_BASEBAND:
	    case MODEM_SCRAMBLE:
	    case MODEM_AIS:
	    default:	/* Not AFSK */
	      {

	      // For AIS we will accept only a good CRC without any fixup attempts.
	      // Even with that, there are still a lot of CRC false matches with random noise.

	      if (save_audio_config_p->achan[chan].modem_type == MODEM_AIS) {
		if (save_audio_config_p->achan[chan].fix_bits != RETRY_NONE) {
	          text_color_set(DW_COLOR_INFO);
		  dw_printf ("Channel %d: FIX_BITS option has been turned off for AIS.\n", chan);
	          save_audio_config_p->achan[chan].fix_bits = RETRY_NONE;
	        }
		if (save_audio_config_p->achan[chan].passall != 0) {
	          text_color_set(DW_COLOR_INFO);
		  dw_printf ("Channel %d: PASSALL option has been turned off for AIS.\n", chan);
	          save_audio_config_p->achan[chan].passall = 0;
	        }
	      }

	      if (strcmp(save_audio_config_p->achan[chan].profiles, "") == 0) {

		/* Apply default if not set earlier. */
		/* Not sure if it should be on for ARM too. */
		/* Need to take a look at CPU usage and performance difference. */

		/* Version 1.5:  Remove special case for ARM. */
		/* We want higher performance to be the default. */
		/* "MODEM 9600 -" can be used on very slow CPU if necessary. */

	        strlcpy (save_audio_config_p->achan[chan].profiles, "+", sizeof(save_audio_config_p->achan[chan].profiles));
	      }


#ifdef TUNE_ZEROSTUFF
	      zerostuff = TUNE_ZEROSTUFF;
#endif


/*
 * We need a minimum number of audio samples per bit time for good performance.
 * Easier to check here because demod_9600_init might have an adjusted sample rate.
 */

	      float ratio = (float)(save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec)
							/ (float)(save_audio_config_p->achan[chan].baud);

/*
 * Set reasonable upsample ratio if user did not override.
 */

	      if (save_audio_config_p->achan[chan].upsample == 0) {

	        if (ratio < 5) {

	          // example: 44100 / 9600 is 4.59
	          // Big improvement with x2.
	          // x4 seems to work the best.
	          // The other parameters are not as touchy.
	          // Might reduce on ARM if it takes too much CPU power.

	          save_audio_config_p->achan[chan].upsample = 4;
	        }
	        else if (ratio < 10) {

	          // 48000 / 9600 is 5.00
	          // Need more reasearch.  Treat like above for now.

	          save_audio_config_p->achan[chan].upsample = 4;
	        }
	        else if (ratio < 15) {

	          // ...

	          save_audio_config_p->achan[chan].upsample = 2;
	        }
	        else {	// >= 15
	          //
	          // An example of this might be .....
	          // Probably no benefit.

	          save_audio_config_p->achan[chan].upsample = 1;
	        }
	      }

#ifdef TUNE_UPSAMPLE
	      save_audio_config_p->achan[chan].upsample = TUNE_UPSAMPLE;
#endif

	      text_color_set(DW_COLOR_DEBUG);
	      dw_printf ("Channel %d: %d baud, %s, %s, %d sample rate x %d",
		    chan,
	            save_audio_config_p->achan[chan].baud,
	            save_audio_config_p->achan[chan].modem_type == MODEM_AIS ? "AIS" : "K9NG/G3RUH",
		    save_audio_config_p->achan[chan].profiles,
		    save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec,
	            save_audio_config_p->achan[chan].upsample);
	      if (save_audio_config_p->achan[chan].dtmf_decode != DTMF_DECODE_OFF) 
	        dw_printf (", DTMF decoder enabled");
	      dw_printf (".\n");
	      
	      struct demodulator_state_s *D;
	      D = &demodulator_state[chan][0];	// first subchannel


	      save_audio_config_p->achan[chan].num_subchan = 1;
              save_audio_config_p->achan[chan].num_slicers = 1;

	      if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) {

		/* I'm not happy about putting this hack here. */
		/* This belongs in demod_9600_init but it doesn't have access to the audio config. */

	        save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
     	      }
	        

	      text_color_set(DW_COLOR_INFO);
	      dw_printf ("The ratio of audio samples per sec (%d) to data rate in baud (%d) is %.1f\n",
				save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec,
				save_audio_config_p->achan[chan].baud,
				(double)ratio);
	      if (ratio < 3) {
	        text_color_set(DW_COLOR_ERROR);
	        dw_printf ("There is little hope of success with such a low ratio.  Use a higher sample rate.\n");
	      }
	      else if (ratio < 5) {
	        dw_printf ("This is on the low side for best performance.  Can you use a higher sample rate?\n");
	        if (save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec == 44100) {
	          dw_printf ("For example, can you use 48000 rather than 44100?\n");
	        }
	      }
	      else if (ratio < 6) {
	        dw_printf ("Increasing the sample rate should improve decoder performance.\n");
	      }
	      else if (ratio > 15) {
	        dw_printf ("Sample rate is more than adequate.  You might lower it if CPU load is a concern.\n");
	      }
	      else {
	        dw_printf ("This is a suitable ratio for good performance.\n");
	      }

	      demod_9600_init (save_audio_config_p->achan[chan].modem_type,
			save_audio_config_p->achan[chan].upsample * save_audio_config_p->adev[ACHAN2ADEV(chan)].samples_per_sec,
			save_audio_config_p->achan[chan].baud, D);

	      if (strchr(save_audio_config_p->achan[chan].profiles, '+') != NULL) {

		/* I'm not happy about putting this hack here. */
		/* should pass in as a parameter rather than adding on later. */

	        save_audio_config_p->achan[chan].num_slicers = MAX_SLICERS;
		D->num_slicers = MAX_SLICERS;
	      }

	      /* For signal level reporting, we want a longer term view. */

	      D->quick_attack = D->agc_fast_attack * 0.2f;
	      D->sluggish_decay = D->agc_slow_decay * 0.2f;
	      }
	      break;

	  }  /* switch on modulation type. */
    
	 }  /* if channel number is valid */

	}  /* for chan ... */


        return (0);

} /* end demod_init */



/*------------------------------------------------------------------
 *
 * Name:        demod_get_sample
 *
 * Purpose:     Get one audio sample fromt the specified sound input source.
 *
 * Inputs:	a	- Index for audio device.  0 = first.
 *
 * Returns:     -32768 .. 32767 for a valid audio sample.
 *              256*256 for end of file or other error.
 *
 * Global In:	save_audio_config_p->adev[ACHAN2ADEV(chan)].bits_per_sample - So we know whether to 
 *			read 1 or 2 bytes from audio stream.
 *
 * Description:	Grab 1 or two btyes depending on data source.
 *
 *		When processing stereo, the caller will call this
 *		at twice the normal rate to obtain alternating left 
 *		and right samples.
 *
 *----------------------------------------------------------------*/

#define FSK_READ_ERR (256*256)


__attribute__((hot))
int demod_get_sample (int a)		
{
	int x1, x2;
	signed short sam;	/* short to force sign extention. */


	assert (save_audio_config_p->adev[a].bits_per_sample == 8 || save_audio_config_p->adev[a].bits_per_sample == 16);


	if (save_audio_config_p->adev[a].bits_per_sample == 8) {

	  x1 = audio_get(a);				
	  if (x1 < 0) return(FSK_READ_ERR);

	  assert (x1 >= 0 && x1 <= 255);

	  /* Scale 0..255 into -32k..+32k */

	  sam = (x1 - 128) * 256;

	}
	else {
	  x1 = audio_get(a);	/* lower byte first */
	  if (x1 < 0) return(FSK_READ_ERR);

	  x2 = audio_get(a);
	  if (x2 < 0) return(FSK_READ_ERR);

	  assert (x1 >= 0 && x1 <= 255);
	  assert (x2 >= 0 && x2 <= 255);

          sam = ( x2 << 8 ) | x1;
	}

	return (sam);
}


/*-------------------------------------------------------------------
 *
 * Name:        demod_process_sample
 *
 * Purpose:     (1) Demodulate the AFSK signal.
 *		(2) Recover clock and data.
 *
 * Inputs:	chan	- Audio channel.  0 for left, 1 for right.
 *		subchan - modem of the channel.
 *		sam	- One sample of audio.
 *			  Should be in range of -32768 .. 32767.
 *
 * Returns:	None 
 *
 * Descripion:	We start off with two bandpass filters tuned to
 *		the given frequencies.  In the case of VHF packet
 *		radio, this would be 1200 and 2200 Hz.
 *
 *		The bandpass filter amplitudes are compared to 
 *		obtain the demodulated signal.
 *
 *		We also have a digital phase locked loop (PLL)
 *		to recover the clock and pick out data bits at
 *		the proper rate.
 *
 *		For each recovered data bit, we call:
 *
 *			  hdlc_rec (channel, demodulated_bit);
 *
 *		to decode HDLC frames from the stream of bits.
 *
 * Future:	This could be generalized by passing in the name
 *		of the function to be called for each bit recovered
 *		from the demodulator.  For now, it's simply hard-coded.
 *
 *--------------------------------------------------------------------*/


__attribute__((hot))
void demod_process_sample (int chan, int subchan, int sam)
{
	float fsam;
	int k;


	struct demodulator_state_s *D;

	assert (chan >= 0 && chan < MAX_CHANS);
	assert (subchan >= 0 && subchan < MAX_SUBCHANS);

	D = &demodulator_state[chan][subchan];


	/* Scale to nice number, actually -2.0 to +2.0 for extra headroom */

	fsam = sam / 16384.0f;

/*
 * Accumulate measure of the input signal level.
 */


/*
 * Version 1.2: Try new approach to capturing the amplitude.
 * This is same as the later AGC without the normalization step.
 * We want decay to be substantially slower to get a longer
 * range idea of the received audio.
 */

	if (fsam >= D->alevel_rec_peak) {
	  D->alevel_rec_peak = fsam * D->quick_attack + D->alevel_rec_peak * (1.0f - D->quick_attack);
	}
	else {
	  D->alevel_rec_peak = fsam * D->sluggish_decay + D->alevel_rec_peak * (1.0f - D->sluggish_decay);
	}

	if (fsam <= D->alevel_rec_valley) {
	  D->alevel_rec_valley = fsam * D->quick_attack + D->alevel_rec_valley * (1.0f - D->quick_attack);
	}
	else  {   
	  D->alevel_rec_valley = fsam * D->sluggish_decay + D->alevel_rec_valley * (1.0f - D->sluggish_decay);
	}


/*
 * Select decoder based on modulation type.
 */

	switch (save_audio_config_p->achan[chan].modem_type) {

	  case MODEM_OFF:

	    // Might have channel only listening to DTMF for APRStt gateway.
	    // Don't waste CPU time running a demodulator here.
	    break;

	  case MODEM_AFSK:
	  case MODEM_EAS:

	    if (save_audio_config_p->achan[chan].decimate > 1) {

	      sample_sum[chan][subchan] += sam;
	      sample_count[chan][subchan]++;
	      if (sample_count[chan][subchan] >= save_audio_config_p->achan[chan].decimate) {
  	        demod_afsk_process_sample (chan, subchan, sample_sum[chan][subchan] / save_audio_config_p->achan[chan].decimate, D);
	        sample_sum[chan][subchan] = 0;
	        sample_count[chan][subchan] = 0;
	      }
	    }
	    else {
	      demod_afsk_process_sample (chan, subchan, sam, D);
	    }
	    break;

	  case MODEM_QPSK:
	  case MODEM_8PSK:

	    if (save_audio_config_p->achan[chan].decimate > 1) {

	      text_color_set(DW_COLOR_ERROR);
	      dw_printf ("Invalid combination of options.  Exiting.\n");
	      // Would probably work but haven't thought about it or tested yet.
	      exit (1);
	    }
	    else {
	      demod_psk_process_sample (chan, subchan, sam, D);
	    }
	    break;

	  case MODEM_BASEBAND:
	  case MODEM_SCRAMBLE:
	  case MODEM_AIS:
	  default:
	
	    if (zerostuff) {
	      /* Literature says this is better if followed */
	      /* by appropriate low pass filter. */
	      /* So far, both are same in tests with different */
	      /* optimal low pass filter parameters. */

	      for (k=1; k<save_audio_config_p->achan[chan].upsample; k++) {
	        demod_9600_process_sample (chan, 0, D);
	      }
	      demod_9600_process_sample (chan, sam * save_audio_config_p->achan[chan].upsample, D);
	    }
	    else {

	      /* Linear interpolation. */
	      static int prev_sam;

	      switch (save_audio_config_p->achan[chan].upsample) {
	        case 1:
	          demod_9600_process_sample (chan, sam, D);
	          break;
	        case 2:
	          demod_9600_process_sample (chan, (prev_sam + sam) / 2, D);
	          demod_9600_process_sample (chan, sam, D);
	          break;
                case 3:
                  demod_9600_process_sample (chan, (2 * prev_sam + sam) / 3, D);
                  demod_9600_process_sample (chan, (prev_sam + 2 * sam) / 3, D);
                  demod_9600_process_sample (chan, sam, D);
                  break;
                case 4:
                  demod_9600_process_sample (chan, (3 * prev_sam + sam) / 4, D);
                  demod_9600_process_sample (chan, (prev_sam + sam) / 2, D);
                  demod_9600_process_sample (chan, (prev_sam + 3 * sam) / 4, D);
                  demod_9600_process_sample (chan, sam, D);
                  break;
                default:
                  assert (0);
                  break;
	      }
	      prev_sam = sam;
	    }
	    break;

	}  /* switch modem_type */
	return;

} /* end demod_process_sample */






/* Doesn't seem right.  Need to revisit this. */
/* Resulting scale is 0 to almost 100. */
/* Cranking up the input level produces no more than 97 or 98. */
/* We currently produce a message when this goes over 90. */

alevel_t demod_get_audio_level (int chan, int subchan) 
{
	struct demodulator_state_s *D;
	alevel_t alevel;

	assert (chan >= 0 && chan < MAX_CHANS);
	assert (subchan >= 0 && subchan < MAX_SUBCHANS);

	/* We have to consider two different cases here. */
	/* N demodulators, each with own slicer and HDLC decoder. */
	/* Single demodulator, multiple slicers each with own HDLC decoder. */

	if (demodulator_state[chan][0].num_slicers > 1) {
	  subchan = 0;
	}

	D = &demodulator_state[chan][subchan];

	// Take half of peak-to-peak for received audio level.

	alevel.rec = (int) (( D->alevel_rec_peak - D->alevel_rec_valley ) * 50.0f + 0.5f);

	if (save_audio_config_p->achan[chan].modem_type == MODEM_AFSK ||
	    save_audio_config_p->achan[chan].modem_type == MODEM_EAS) {

	  /* For AFSK, we have mark and space amplitudes. */

	  alevel.mark = (int) ((D->alevel_mark_peak ) * 100.0f + 0.5f);
	  alevel.space = (int) ((D->alevel_space_peak ) * 100.0f + 0.5f);
	}
	else if (save_audio_config_p->achan[chan].modem_type == MODEM_QPSK ||
	         save_audio_config_p->achan[chan].modem_type == MODEM_8PSK) {
	  alevel.mark = -1;
	  alevel.space = -1;
	}
	else {

#if 1	
	  /* Display the + and - peaks.  */
	  /* Normally we'd expect them to be about the same. */
	  /* However, with SDR, or other DC coupling, we could have an offset. */

	  alevel.mark = (int) ((D->alevel_mark_peak) * 200.0f  + 0.5f);
	  alevel.space = (int) ((D->alevel_space_peak) * 200.0f - 0.5f);


#else
	  /* Here we have + and - peaks after filtering. */
	  /* Take half of the peak to peak. */
	  /* The "5/6" factor worked out right for the current low pass filter. */
	  /* Will it need to be different if the filter is tweaked? */

	  alevel.mark = (int) ((D->alevel_mark_peak - D->alevel_space_peak) * 100.0f * 5.0f/6.0f + 0.5f);
	  alevel.space = -1;		/* to print one number inside of ( ) */
#endif
	}
	return (alevel);
}


/* end demod.c */
