예제 #1
0
//***************************************************************************
int main(int argc, char *argv[])
{
  extern char *optarg;
  extern int optind;
  int i,j,k;
  unsigned char *symbols, *decdata;
  signed char message[]={-9,13,-35,123,57,-39,64,0,0,0,0};
  char *callsign,*grid,*grid6, *call_loc_pow, *cdbm;
  char *ptr_to_infile,*ptr_to_infile_suffix;
  char uttime[5],date[7];
  char xuttime[6],xdate[11];
  int c,delta,nfft2=65536,verbose=0,quickmode=0,writenoise=0,usehashtable=1;
  int shift1, lagmin, lagmax, lagstep, worth_a_try, not_decoded, nadd, ndbm;
  int32_t n1, n2, n3;
  unsigned int nbits;
  unsigned int npoints, metric, maxcycles, cycles, maxnp;
  float df=375.0/256.0/2;
  float freq0[200],snr0[200],drift0[200],sync0[200];
  int shift0[200];
  float dt=1.0/375.0;
  double dialfreq_cmdline=0.0, dialfreq;
  float dialfreq_error=0.0;
  float fmin=-110, fmax=110;
  float f1, fstep, sync1, drift1, tblank=0, fblank=0;
  double *idat, *qdat;
  clock_t t0,t00;
  double tfano=0.0,treadwav=0.0,tcandidates=0.0,tsync0=0.0;
  double tsync1=0.0,tsync2=0.0,ttotal=0.0;

// Parameters used for performance-tuning:
  maxcycles=10000;                         //Fano timeout limit
  double minsync1=0.10;                    //First sync limit
  double minsync2=0.12;                    //Second sync limit
  int iifac=3;                             //Step size in final DT peakup
  int symfac=45;                           //Soft-symbol normalizing factor
  int maxdrift=4;                          //Maximum (+/-) drift
  double minrms=52.0 * (symfac/64.0);      //Final test for palusible decoding
  delta=60;                                //Fano threshold step

  t00=clock();
  fftw_complex *fftin, *fftout;
#include "./mettab.c"

// Check for an optional FFTW wisdom file
  FILE *fp_fftw_wisdom_file;
  if ((fp_fftw_wisdom_file = fopen("fftw_wisdom_wsprd", "r"))) {
    fftw_import_wisdom_from_file(fp_fftw_wisdom_file);
    fclose(fp_fftw_wisdom_file);
  }

  idat=malloc(sizeof(double)*nfft2);
  qdat=malloc(sizeof(double)*nfft2);

  while ( (c = getopt(argc, argv, "b:e:f:Hnqt:wv")) !=-1 ) {
    switch (c) {
    case 'b':
      fblank = strtof(optarg,NULL);
      break;
    case 'e':
      dialfreq_error = strtof(optarg,NULL);   // units of Hz
      // dialfreq_error = dial reading - actual, correct frequency
      break;
    case 'f':
      dialfreq_cmdline = strtod(optarg,NULL); // units of MHz
      break;
    case 'H':
      usehashtable = 0;
      break;
    case 'n':
      writenoise = 1;
      break;
    case 'q':
      quickmode = 1;
      break;
    case 't':
      tblank = strtof(optarg,NULL);
      break;
    case 'v':
      verbose = 1;
      break;
    case 'w':
      fmin=-150.0;
      fmax=150.0;
      break;
    case '?':
      usage();
      return 1;
    }
  }

  if( optind+1 > argc) {
    usage();
    return 1;
  } else {
    ptr_to_infile=argv[optind];
  }

  FILE *fall_wspr, *fwsprd, *fhash, *ftimer, *fweb;
  FILE *fdiag;
  fall_wspr=fopen("ALL_WSPR.TXT","a");
  fwsprd=fopen("wsprd.out","w");
  fdiag=fopen("wsprd_diag","a");
  fweb=fopen("wspr-now.txt","a");

  if((ftimer=fopen("wsprd_timer","r"))) {
    //Accumulate timing data
    nr=fscanf(ftimer,"%lf %lf %lf %lf %lf %lf %lf",
	   &treadwav,&tcandidates,&tsync0,&tsync1,&tsync2,&tfano,&ttotal);
    fclose(ftimer);
  }
  ftimer=fopen("wsprd_timer","w");

  if( strstr(ptr_to_infile,".wav") ) {
    ptr_to_infile_suffix=strstr(ptr_to_infile,".wav");

    t0 = clock();
    npoints=readwavfile(ptr_to_infile, idat, qdat);
    treadwav += (double)(clock()-t0)/CLOCKS_PER_SEC;

    if( npoints == 1 ) {
      return 1;
    }
    dialfreq=dialfreq_cmdline - (dialfreq_error*1.0e-06);
  } else if ( strstr(ptr_to_infile,".c2") !=0 )  {
    ptr_to_infile_suffix=strstr(ptr_to_infile,".c2");
    npoints=readc2file(ptr_to_infile, idat, qdat, &dialfreq);
    if( npoints == 1 ) {
      return 1;
    }
    dialfreq -= (dialfreq_error*1.0e-06);
  } else {
    printf("Error: Failed to open %s\n",ptr_to_infile);
    printf("WSPR file must have suffix .wav or .c2\n");
    return 1;
  }

// Parse date and time from given filename
  strncpy(date,ptr_to_infile_suffix-11,6);
  strncpy(uttime,ptr_to_infile_suffix-4,4);
  date[6]='\0';
  uttime[4]='\0';
//added riyas
  sprintf(xdate, "20%.2s-%.2s-%.2s", date, date+2, date+4);
  xdate[10]='\0';
  sprintf(xuttime, "%.2s:%.2s", uttime, uttime+2);
  xuttime[5]='\0';

// Do windowed ffts over 2 symbols, stepped by half symbols
  int nffts=4*floor(npoints/512)-1;
  fftin=(fftw_complex*) fftw_malloc(sizeof(fftw_complex)*512);
  fftout=(fftw_complex*) fftw_malloc(sizeof(fftw_complex)*512);
  PLAN3 = fftw_plan_dft_1d(512, fftin, fftout, FFTW_FORWARD, PATIENCE);
    
  float ps[512][nffts];
  float w[512];
  for(i=0; i<512; i++) {
    w[i]=sin(0.006135923*i);
  }

  memset(ps,0.0, sizeof(float)*512*nffts);
  for (i=0; i<nffts; i++) {
    for(j=0; j<512; j++ ) {
      k=i*128+j;
      fftin[j][0]=idat[k] * w[j];
      fftin[j][1]=qdat[k] * w[j];
    }
    fftw_execute(PLAN3);
    for (j=0; j<512; j++ ) {
      k=j+256;
      if( k>511 )
	k=k-512;
      ps[j][i]=fftout[k][0]*fftout[k][0]+fftout[k][1]*fftout[k][1];
    }
  }

  fftw_free(fftin);
  fftw_free(fftout);

// Compute average spectrum
  float psavg[512];
  memset(psavg,0.0, sizeof(float)*512);
  for (i=0; i<nffts; i++) {
    for (j=0; j<512; j++) {
      psavg[j]=psavg[j]+ps[j][i];
    }
  }

// Smooth with 7-point window and limit spectrum to +/-150 Hz
  int window[7]={1,1,1,1,1,1,1};
  float smspec[411];
  for (i=0; i<411; i++) {
    smspec[i]=0.0;
    for(j=-3; j<=3; j++) {
      k=256-205+i+j;
      smspec[i]=smspec[i]+window[j+3]*psavg[k];
    }
  }

// Sort spectrum values, then pick off noise level as a percentile
  float tmpsort[411];
  for (j=0; j<411; j++) {
    tmpsort[j]=smspec[j];
  }
  qsort(tmpsort, 411, sizeof(float), floatcomp);

// Noise level of spectrum is estimated as 123/411= 30'th percentile
  float noise_level = tmpsort[122];

// Renormalize spectrum so that (large) peaks represent an estimate of snr
  float min_snr_neg33db = pow(10.0,(-33+26.5)/10.0);
  for (j=0; j<411; j++) {
    smspec[j]=smspec[j]/noise_level - 1.0;
    if( smspec[j] < min_snr_neg33db) smspec[j]=0.1;
    continue;
  }

// Find all local maxima in smoothed spectrum.
  for (i=0; i<200; i++) {
    freq0[i]=0.0;
    snr0[i]=0.0;
    drift0[i]=0.0;
    shift0[i]=0;
    sync0[i]=0.0;
  }

  int npk=0;
  for(j=1; j<410; j++) {
    if((smspec[j]>smspec[j-1]) && (smspec[j]>smspec[j+1]) && (npk<200)) {
      freq0[npk]=(j-205)*df;
      snr0[npk]=10*log10(smspec[j])-26.5;
      npk++;
    }
  }

// Compute corrected fmin, fmax, accounting for dial frequency error
  fmin += dialfreq_error;    // dialfreq_error is in units of Hz
  fmax += dialfreq_error;

// Don't waste time on signals outside of the range [fmin,fmax].
  i=0;
  for( j=0; j<npk; j++) {
    if( freq0[j] >= fmin && freq0[j] <= fmax ) {
      freq0[i]=freq0[j];
      snr0[i]=snr0[j];
      i++;
    }
  }
  npk=i;

  t0=clock();
/* Make coarse estimates of shift (DT), freq, and drift

  * Look for time offsets up to +/- 8 symbols (about +/- 5.4 s) relative 
    to nominal start time, which is 2 seconds into the file

  * Calculates shift relative to the beginning of the file

  * Negative shifts mean that signal started before start of file

  * The program prints DT = shift-2 s

  * Shifts that cause sync vector to fall off of either end of the data 
    vector are accommodated by "partial decoding", such that missing 
    symbols produce a soft-decision symbol value of 128 

  * The frequency drift model is linear, deviation of +/- drift/2 over the
    span of 162 symbols, with deviation equal to 0 at the center of the 
    signal vector. 
*/

  int idrift,ifr,if0,ifd,k0;
  int kindex;
  float smax,ss,pow,p0,p1,p2,p3;
  for(j=0; j<npk; j++) {                              //For each candidate...
    smax=-1e30;
    if0=freq0[j]/df+256;
    for (ifr=if0-1; ifr<=if0+1; ifr++) {                      //Freq search
      for( k0=-10; k0<22; k0++) {                             //Time search
	for (idrift=-maxdrift; idrift<=maxdrift; idrift++) {  //Drift search
	  ss=0.0;
	  pow=0.0;
	  for (k=0; k<162; k++) {                             //Sum over symbols
	    ifd=ifr+((float)k-81.0)/81.0*( (float)idrift )/(2.0*df);
	    kindex=k0+2*k;
	    if( kindex < nffts ) {
	      p0=ps[ifd-3][kindex];
	      p1=ps[ifd-1][kindex];
	      p2=ps[ifd+1][kindex];
	      p3=ps[ifd+3][kindex];

	      p0=sqrt(p0);
	      p1=sqrt(p1);
	      p2=sqrt(p2);
	      p3=sqrt(p3);

	      ss=ss+(2*pr3[k]-1)*((p1+p3)-(p0+p2));
	      pow=pow+p0+p1+p2+p3;
	      sync1=ss/pow;
	    }
	  }
	  if( sync1 > smax ) {                  //Save coarse parameters
	    smax=sync1;
	    shift0[j]=128*(k0+1);
	    drift0[j]=idrift;
	    freq0[j]=(ifr-256)*df;
	    sync0[j]=sync1;
	  }
	}
      }
    }
  }
  tcandidates += (double)(clock()-t0)/CLOCKS_PER_SEC;

  nbits=81;
  symbols=malloc(sizeof(char)*nbits*2);
  memset(symbols,0,sizeof(char)*nbits*2);
  decdata=malloc((nbits+7)/8);
  grid=malloc(sizeof(char)*5);
  grid6=malloc(sizeof(char)*7);
  callsign=malloc(sizeof(char)*13);
  call_loc_pow=malloc(sizeof(char)*23);
  cdbm=malloc(sizeof(char)*3);
  float allfreqs[npk];
  memset(allfreqs,0,sizeof(float)*npk);
  char allcalls[npk][13];
  memset(allcalls,0,sizeof(char)*npk*13);
  memset(grid,0,sizeof(char)*5);
  memset(grid6,0,sizeof(char)*7);
  memset(callsign,0,sizeof(char)*13);
  memset(call_loc_pow,0,sizeof(char)*23);
  memset(cdbm,0,sizeof(char)*3);
  char hashtab[32768][13];
  memset(hashtab,0,sizeof(char)*32768*13);
  uint32_t nhash( const void *, size_t, uint32_t);
  int nh;
    
  if( usehashtable ) {
    char line[80], hcall[12];
    if( (fhash=fopen("hashtable.txt","r+")) ) {
      while (fgets(line, sizeof(line), fhash) != NULL) {
	sscanf(line,"%d %s",&nh,hcall);
	strcpy(*hashtab+nh*13,hcall);
      }
    } else {
      fhash=fopen("hashtable.txt","w+");
    }
    fclose(fhash);
  }
    
  int uniques=0, noprint=0;
/*    
 Refine the estimates of freq, shift using sync as a metric.
 Sync is calculated such that it is a float taking values in the range
 [0.0,1.0].
        
 Function sync_and_demodulate has three modes of operation
 mode is the last argument:

      0 = no frequency or drift search. find best time lag.
      1 = no time lag or drift search. find best frequency.
      2 = no frequency or time lag search. Calculate soft-decision 
          symbols using passed frequency and shift.

NB: best possibility for OpenMP may be here: several worker threads
could each work on one candidate at a time.
*/

  for (j=0; j<npk; j++) {
    f1=freq0[j];
    drift1=drift0[j];
    shift1=shift0[j];
    sync1=sync0[j];

// Fine search for best sync lag (mode 0)
    fstep=0.0;
    lagmin=shift1-144;
    lagmax=shift1+144;
    lagstep=8;
    if(quickmode) lagstep=16;
    t0 = clock();
    sync_and_demodulate(idat, qdat, npoints, symbols, &f1, fstep, &shift1, 
		    lagmin, lagmax, lagstep, &drift1, symfac, &sync1, 0);
    tsync0 += (double)(clock()-t0)/CLOCKS_PER_SEC;

// Fine search for frequency peak (mode 1)
    fstep=0.1;
    t0 = clock();
    sync_and_demodulate(idat, qdat, npoints, symbols, &f1, fstep, &shift1, 
		     lagmin, lagmax, lagstep, &drift1, symfac, &sync1, 1);
    tsync1 += (double)(clock()-t0)/CLOCKS_PER_SEC;

    if( sync1 > minsync1 ) {
      worth_a_try = 1;
    } else {
      worth_a_try = 0;
    }

    int idt=0, ii=0, jiggered_shift;
    uint32_t ihash;
    double y,sq,rms;
    not_decoded=1;

    while ( worth_a_try && not_decoded && idt<=(128/iifac)) {
      ii=(idt+1)/2;
      if( idt%2 == 1 ) ii=-ii;
      ii=iifac*ii;
      jiggered_shift=shift1+ii;

// Use mode 2 to get soft-decision symbols
      t0 = clock();
      sync_and_demodulate(idat, qdat, npoints, symbols, &f1, fstep, 
	 &jiggered_shift, lagmin, lagmax, lagstep, &drift1, symfac, 
			  &sync1, 2);
      tsync2 += (double)(clock()-t0)/CLOCKS_PER_SEC;

      sq=0.0;
      for(i=0; i<162; i++) {
	y=(double)symbols[i] - 128.0;
	sq += y*y;
      }
      rms=sqrt(sq/162.0);

      if((sync1 > minsync2) && (rms > minrms)) {
	deinterleave(symbols);
	t0 = clock();
	  not_decoded = fano(&metric,&cycles,&maxnp,decdata,symbols,nbits,
			     mettab,delta,maxcycles);
	tfano += (double)(clock()-t0)/CLOCKS_PER_SEC;

	/* ### Used for timing tests:
	if(not_decoded) fprintf(fdiag,
	    "%6s %4s %4.1f %3.0f %4.1f %10.7f  %-18s %2d %5u %4d %6.1f %2d\n",
	    date,uttime,sync1*10,snr0[j], shift1*dt-2.0, dialfreq+(1500+f1)/1e6,
	    "@                 ", (int)drift1, cycles/81, ii, rms, maxnp);
	*/
      }
      idt++;
      if( quickmode ) break;  
    }

    if( worth_a_try && !not_decoded ) {
      for(i=0; i<11; i++) {
	if( decdata[i]>127 ) {
	  message[i]=decdata[i]-256;
	} else {
	  message[i]=decdata[i];
	}
      }

      unpack50(message,&n1,&n2);
      unpackcall(n1,callsign);
      unpackgrid(n2, grid);
      int ntype = (n2&127) - 64;

/*
 Based on the value of ntype, decide whether this is a Type 1, 2, or 
 3 message.

 * Type 1: 6 digit call, grid, power - ntype is positive and is a member 
         of the set {0,3,7,10,13,17,20...60}

 * Type 2: extended callsign, power - ntype is positive but not
         a member of the set of allowed powers

 * Type 3: hash, 6 digit grid, power - ntype is negative.
*/

      if( (ntype >= 0) && (ntype <= 62) ) {
	int nu=ntype%10;
	if( nu == 0 || nu == 3 || nu == 7 ) {
	  ndbm=ntype;
	  memset(call_loc_pow,0,sizeof(char)*23);
	  sprintf(cdbm,"%2d",ndbm);
	  strncat(call_loc_pow,callsign,strlen(callsign));
	  strncat(call_loc_pow," ",1);
	  strncat(call_loc_pow,grid,4);
	  strncat(call_loc_pow," ",1);
	  strncat(call_loc_pow,cdbm,2);
	  strncat(call_loc_pow,"\0",1);
                    
	  ihash=nhash(callsign,strlen(callsign),(uint32_t)146);
	  strcpy(*hashtab+ihash*13,callsign);

	  noprint=0;
	} else {
	  nadd=nu;
	  if( nu > 3 ) nadd=nu-3;
	  if( nu > 7 ) nadd=nu-7;
	  n3=n2/128+32768*(nadd-1);
	  unpackpfx(n3,callsign);
	  ndbm=ntype-nadd;

	  memset(call_loc_pow,0,sizeof(char)*23);
	  sprintf(cdbm,"%2d",ndbm);
	  strncat(call_loc_pow,callsign,strlen(callsign));
	  strncat(call_loc_pow," ",1);
	  strncat(call_loc_pow,cdbm,2);
	  strncat(call_loc_pow,"\0",1);
                    
	  ihash=nhash(callsign,strlen(callsign),(uint32_t)146);
	  strcpy(*hashtab+ihash*13,callsign);

	  noprint=0;
	}
      } else if ( ntype < 0 ) {
	ndbm=-(ntype+1);
	memset(grid6,0,sizeof(char)*7);
	strncat(grid6,callsign+5,1);
	strncat(grid6,callsign,5);
	ihash=(n2-ntype-64)/128;
	if( strncmp(hashtab[ihash],"\0",1) != 0 ) {
	  sprintf(callsign,"<%s>",hashtab[ihash]);
	} else {
	  sprintf(callsign,"%5s","<...>");
	}

	memset(call_loc_pow,0,sizeof(char)*23);
	sprintf(cdbm,"%2d",ndbm);
	strncat(call_loc_pow,callsign,strlen(callsign));
	strncat(call_loc_pow," ",1);
	strncat(call_loc_pow,grid6,strlen(grid6));
	strncat(call_loc_pow," ",1);
	strncat(call_loc_pow,cdbm,2);
	strncat(call_loc_pow,"\0",1);
                
	noprint=0;
                
// I don't know what to do with these... They show up as "A000AA" grids.
	if( ntype == -64 ) noprint=1;
	
      }
            
// Remove dupes (same callsign and freq within 1 Hz)
      int dupe=0;
      for (i=0; i<npk; i++) {
	if(!strcmp(callsign,allcalls[i]) && 
	   (fabs(f1-allfreqs[i]) <1.0)) dupe=1;
      }
      if( (verbose || !dupe) && !noprint) {
	uniques++;
	strcpy(allcalls[uniques],callsign);
	allfreqs[uniques]=f1;
// Add an extra space at the end of each line so that wspr-x doesn't 
// truncate the power (TNX to DL8FCL!)
    char mygrid[]="NK03";
	char mycall[]="SIARS";
    //printf("%4s=================%d\n",grid,distance(grid, mygrid));
	
    
	printf("%4s %3.0f %4.1f %10.6f %2d  %-s \n",
	       uttime, snr0[j],(shift1*dt-2.0), dialfreq+(1500+f1)/1e6,
	       (int)drift1, call_loc_pow);

	fprintf(fall_wspr,
		"%6s %4s %3.0f %3.0f %4.1f %10.7f  %-22s %2d %5u %4d\n",
		date,uttime,sync1*10,snr0[j],
		shift1*dt-2.0, dialfreq+(1500+f1)/1e6,
		call_loc_pow, (int)drift1, cycles/81, ii);
		
	fprintf(fwsprd,
		"%6s %4s %3.0f %3.0f %4.1f %10.7f  %-22s %2d %5u %4d\n",
		date,uttime,sync1*10,snr0[j],
		shift1*dt-2.0, dialfreq+(1500+f1)/1e6,
		call_loc_pow, (int)drift1, cycles/81, ii);		

	fprintf(fweb,"&nbsp;%10s %5s&nbsp;&nbsp;%s&nbsp;&nbsp;%10.7f&nbsp;&nbsp;%3.0f&nbsp;&nbsp;%2d&nbsp;&nbsp;%4s&nbsp;&nbsp;%2d&nbsp;&nbsp;%2d&nbsp;&nbsp;%5s&nbsp;&nbsp;%4s&nbsp;&nbsp;%d&nbsp;&nbsp;%d&nbsp;\n",
		xdate,xuttime,callsign,dialfreq+(1500+f1)/1e6,snr0[j],(int)drift1,grid,ndbm,ndbm,mycall,mygrid,distance(grid, mygrid),distance(grid, mygrid));

/* For timing tests

	fprintf(fdiag,
	  "%6s %4s %4.1f %3.0f %4.1f %10.7f  %-18s %2d %5u %4d %6.1f\n",
	  date,uttime,sync1*10,snr0[j],
	  shift1*dt-2.0, dialfreq+(1500+f1)/1e6,
	  call_loc_pow, (int)drift1, cycles/81, ii, rms);
*/
      }
    }
  }
  printf("<DecodeFinished>\n");

  if ((fp_fftw_wisdom_file = fopen("fftw_wisdom_wsprd", "w"))) {
    fftw_export_wisdom_to_file(fp_fftw_wisdom_file);
    fclose(fp_fftw_wisdom_file);
  }

  ttotal += (double)(clock()-t00)/CLOCKS_PER_SEC;

  fprintf(ftimer,"%7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n\n",
	  treadwav,tcandidates,tsync0,tsync1,tsync2,tfano,ttotal);

  fprintf(ftimer,"Code segment        Seconds   Frac\n");
  fprintf(ftimer,"-----------------------------------\n");
  fprintf(ftimer,"readwavfile        %7.2f %7.2f\n",treadwav,treadwav/ttotal);
  fprintf(ftimer,"Coarse DT f0 f1    %7.2f %7.2f\n",tcandidates,
	                                            tcandidates/ttotal);
  fprintf(ftimer,"sync_and_demod(0)  %7.2f %7.2f\n",tsync0,tsync0/ttotal);
  fprintf(ftimer,"sync_and_demod(1)  %7.2f %7.2f\n",tsync1,tsync1/ttotal);
  fprintf(ftimer,"sync_and_demod(2)  %7.2f %7.2f\n",tsync2,tsync2/ttotal);
  fprintf(ftimer,"Fano decoder       %7.2f %7.2f\n",tfano,tfano/ttotal);
  fprintf(ftimer,"-----------------------------------\n");
  fprintf(ftimer,"Total              %7.2f %7.2f\n",ttotal,1.0);

  fclose(fall_wspr);
  fclose(fwsprd);
  fclose(fdiag);
  fclose(ftimer);
  fftw_destroy_plan(PLAN1);
  fftw_destroy_plan(PLAN2);
  fftw_destroy_plan(PLAN3);

  if( usehashtable ) {
    fhash=fopen("hashtable.txt","w");
    for (i=0; i<32768; i++) {
      if( strncmp(hashtab[i],"\0",1) != 0 ) {
	fprintf(fhash,"%5d %s\n",i,*hashtab+i*13);
      }
    }
    fclose(fhash);
  }
  if(fblank+tblank+writenoise == 999) return -1;  //Silence compiler warning
  return 0;
}
예제 #2
0
int main(int argc,char *argv[]){
  int i;
  char *locale;
  unsigned char symbols[2*FRAMESYMBOLS];
  void *vd = NULL;
  unsigned char data[FRAMEBITS/8];
  int sync_start;
  int symcount;
  int lock;
  unsigned long long total_symbols = 0;
  int mettab[2][256];
  long long frames = 1;
  enum { NONE=0, VITERBI, FANO } decoder;

  // I've always hated large numbers displayed without commas. You go blind counting digits.
  // The format strings include the (') flag so setting the locale will cause all large
  // numbers to be displayed with the local thousands separator.
  // In the US (default), this is a comma (,)
  if((locale = getenv("LANG")) != NULL)
    setlocale(LC_ALL,locale);
  else
    setlocale(LC_ALL,"en_US.utf8");

  // Set defaults
  No_bad_frames = 0;
  Symrate = 1024;
  Fano_scale = 8;
  Fano_maxcycles = 100;
  Fano_delta = 4 * Fano_scale;
  Viterbi_enabled = 1;
  Fano_enabled = 1;
  Persistent = 0;

  while((i = getopt(argc,argv,"nFVvr:s:m:d:p")) != EOF){
    switch(i){
    case 'p':
      Persistent = 1;
      break;
    case 'n':
      No_bad_frames = 1;
      break;
    case 'F':
      Viterbi_enabled = 0;
      break;
    case 'V':
      Fano_enabled = 0;
      break;
    case 'v':
      Verbose++;
      break;
    case 'r':
      Symrate = atof(optarg);
      break;
    case 's':
      Fano_scale = atof(optarg);
      break;
    case 'm':
      Fano_maxcycles = atol(optarg);
      break;
    case 'd':
      Fano_delta = atoi(optarg);
      break;
    default:
      printf("usage: %s [-F] [-V] [-v] [-r symrate] [-s fano_scale] [-m fano_maxcycles] [-d fano_delta]\n",
	     argv[0]);
    }
  }
  printf("%s: Fano %s; Viterbi %s\n",argv[0],
	 Fano_enabled ? "enabled" : "disabled",
	 Viterbi_enabled ? "enabled" : "disabled");

  if(No_bad_frames)
    printf("%s: Not displaying bad frames\n",argv[0]);

  if(!Fano_enabled && !Viterbi_enabled){
    printf("%s: Specify only one of -F or -V\n",argv[0]);
    exit(1);
  }
  if(Fano_enabled){
    // Set up Fano decoder
    double noise_amp,sig_amp,total_amp;
    double est_esn0;
    
    // Ideally we'd have the independent signal and noise amplitudes, but we only
    // have the total amplitude as scaled by symdemod
    // Compute signal and noise amplitudes assuming operation at decoder threshold, Eb/No = 3dB
    total_amp = 100.; // Total amplitude set by symdemod
    est_esn0 = 1.0; // Es/No = 0 dB; Eb/No = 3 dB
    noise_amp = total_amp/sqrt(1 + 2*est_esn0);
    sig_amp = noise_amp * sqrt(2*est_esn0);
    
    printf("%s: Fano decoder params: delta %'d; scale %'.1lf; maxcycles %'lu; signal %.1lf; noise %.1lf\n",
	   argv[0],Fano_delta,Fano_scale,Fano_maxcycles,sig_amp,noise_amp);
    gen_met(mettab,sig_amp,noise_amp,0.5,Fano_scale);
  } else if(Viterbi_enabled){
    // Only Viterbi is enabled, set up decoder just once since we'll use its memory constantly
    // When we do Fano with Viterbi fallback, the memory is allocated only when actually needed
    if((vd = create_viterbi224(FRAMEBITS)) == NULL){
      printf("%s: Fano decoding disabled but cannot alloc %d * 1 MB + 2 * 16 MB = %.1lf GB RAM for Viterbi decoder!\nTry -F for Fano only or the default of Fano with Viterbi fallback to allocate memory only when the Viterbi decoder is actually used.\n",
	     argv[0],FRAMEBITS,(FRAMEBITS+32)/1024.);
      exit(2);
    }
  }
  sync_start = -1;
  symcount = 0;
  lock = 0;
  memset(symbols,0,sizeof(symbols));
  while(1){
    int decode_result;

    // Refill buffer with 1 frame + 1 sync length
    if(symcount < FRAMESYMBOLS+SYNCBITS){
      int n,cnt;

      cnt = FRAMESYMBOLS+SYNCBITS-symcount;
      n = fread(&symbols[symcount],sizeof(*symbols),cnt,stdin);
      if(n < cnt)
	goto done;
      symcount += n;
    }
    if(!lock){ // If the last frame decoded we don't need to do this; its sync should be right at 0.
      int record_sum,i;

      record_sum = -1000000;
      for(i=0;i<FRAMESYMBOLS;i++){
	// Run sync correlator over 1 frame
	int sync_sum,k;
	
	sync_sum = 0;
	for(k=0;k<SYNCBITS;k++){
	  int sym;
	  
	  sym = (int)symbols[i + k] - 128;
	  sync_sum += sync_vector[k] ? sym : -sym;
	}	  
	if(sync_sum > record_sum){
	  record_sum = sync_sum;
	  sync_start = i;
	}
      }
      // sync_start now points to the start of the sync of the frame before the current one
      // Make sure we have one frame of symbols past the sync
      if(symcount < sync_start + FRAMESYMBOLS+SYNCBITS){
	int n,cnt;

	cnt = sync_start + FRAMESYMBOLS+SYNCBITS-symcount;
	n = fread(&symbols[symcount],sizeof(*symbols),cnt,stdin);
	if(n < cnt)
	  goto done;
	symcount += n;
      }
    }
    decoder = NONE;
    decode_result = 0;
    if(Fano_enabled){
      // Try a Fano decode
      unsigned long metric,cycles;

      decoder = FANO;      
      memset(data,0,sizeof(data)); // Wipe out previous data
      decode_result = fano(&metric,&cycles,data,&symbols[sync_start+SYNCBITS],FRAMEBITS,mettab,Fano_delta,100,
			   SYNCWORD & 0xffffff,SYNCWORD & 0xffffff);

#if 0
      printf("%s: Fano returns %d; metric %'ld; cycles %'ld\n",argv[0],decode_result,metric,cycles);
#endif
    }
    if(Viterbi_enabled && (!Fano_enabled || ((Persistent || lock) && decode_result != FRAMEBITS))){
      // Decode with Viterbi if:
      // 1. Viterbi is enabled AND either
      // 2. Fano is disabled OR
      // 3. Fano tried and failed, AND the previous frame decoded with either Fano or Viterbi, OR
      // 4  the -p (persistent) option is selected
      // Alloc memory for decoder if not already done
      if(vd == NULL && (vd = create_viterbi224(FRAMEBITS)) == NULL){
	printf("%s: Cannot malloc %d * 1 MB + 2 * 16 MB = %.1lf GB RAM for Viterbi decoder! If this repeats, try Fano only (-F)\n",
	       argv[0],FRAMEBITS,(FRAMEBITS+32)/1024.);
      } else {
	init_viterbi224(vd,SYNCWORD & 0xffffff); // Known starting state after sync
	update_viterbi224_blk(vd,&symbols[sync_start+SYNCBITS],FRAMEBITS);
	chainback_viterbi224(vd,data,FRAMEBITS,SYNCWORD & 0xffffff);
	decoder = VITERBI;
	decode_result = FRAMEBITS;
	if(Fano_enabled){
	  // Don't hold onto all that RAM if we only use Viterbi as a fallback to Fano
	  delete_viterbi224(vd);
	  vd = NULL;
	}
      }
    }
    // If a decoder was tried and thinks it succeeded (Viterbi always does),
    // see if the decoded frame ends with the 5-byte sync sequence.
    // The last 23 bits will always be there since we force them in the decoders
    // but the 17 bits before them are actual data.
    lock = 0;
    if(decode_result == FRAMEBITS){
      unsigned long long lastword;
      int i;

      lastword = 0;
      for(i=123;i<128;i++)
	lastword = (lastword << 8) | data[i];
      
      if(lastword == SYNCWORD)
	lock = 1;      // Good frame
    }
    // Dump data
    if(lock || !No_bad_frames){
      unsigned long long start_symbol;
      int i;

      start_symbol = total_symbols + sync_start + SYNCBITS;
      printf("Frame %'llu at symbol %'llu (%s) with %s %s\n",
	     frames,start_symbol,format_hms(start_symbol/Symrate),
	     decoder == VITERBI ? "Viterbi" : decoder == FANO ? "Fano" : "None",
	     !lock ? "(bad)" : "");
      for(i=0;i<FRAMEBITS/8;i++){
	printf("%02x",data[i]);
	if((i % 16) == 15)
	  putchar('\n');
	else
	  putchar(' ');
      }
      putchar('\n'); // Blank line between frames
      fflush(stdout);
    }
    frames++; // Count good and bad frames
    // Purge frame we just decoded
    {
      int adjust;

      adjust = sync_start + FRAMESYMBOLS;
      assert(symcount >= adjust);
      memmove(symbols,&symbols[adjust],
	      sizeof(*symbols)*(symcount - adjust));
      symcount -= adjust;
      sync_start = 0;
      total_symbols += adjust;
      // The symbol buffer now starts with the 34-bit sync sequence of the frame we just decoded
    }
  }
done:;
  if(vd != NULL){
    delete_viterbi224(vd);
    vd = NULL;
  }
  exit(0);
}