//	(Tell emacs it's -*- C -*- mode)
//
//	File format specific code
//
//        Copyright (c) 2002 Jim Peters.  Released under the GNU
//        GPL version 2.  See the file COPYING for details.
//
//	To add a new format, you will need to write a setup routine
//	for your format which recognises whatever format-tag you
//	choose, and one or more read routines.  Add your setup routine
//	to the format_list[] array at the end of the file, with a
//	short description of the format for user help.  That's it.
//

//
//	Format-specific read routines.
//
//	  len= read_*(BWFile *ff, BWBlock *bb, FILE *in, float **chan,
//	              char *err, int max);
//
//	These routines should read a maximum of 'max' samples (usually
//	1024) from stream 'in', which will already have been correctly
//	positioned in the file, and should write the results into
//	chan[] and err[].  Note that err[] will have been zeroed
//	before this call is made, so it only needs to be modified if
//	errors are detected.  Integer input data should be scaled so
//	that the maximum scale range fits in the range -1 to +1,
//	centred on 0.
//
//	The number of samples read should be returned, and this will
//	be automatically written into bb->len.  The file position
//	after this call should be the position ready to read the next
//	sample (i.e. if you do read-ahead, you'll need to undo it)
//	because this position will be stored and used to read the next
//	block.
//

static int 
read_jm2(BWFile *ff, BWBlock *bb, FILE *in, float **chan, char *err, int max) {
   int len= 0;
   
   while (len < max) {
      int ch;
      while (1) {
	 ch= fgetc(in);
	 if (ch == 3) break;
	 if (ch == EOF) return len; 
	 err[len]= 1;		// Mark sync error
      }
      chan[0][len]= (fgetc(in) - 128) * (1.0 / 128.0);
      chan[1][len]= (fgetc(in) - 128) * (1.0 / 128.0);
      len++;
   }
   return len;
}

static int 
read_jm4(BWFile *ff, BWBlock *bb, FILE *in, float **chan, char *err, int max) {
   int len= 0;
   
   while (len < max) {
      int ch;
      while (1) {
	 ch= fgetc(in);
	 if (ch == 3) break;
	 if (ch == EOF) return len; 
	 err[len]= 1;		// Mark sync error
      }
      chan[0][len]= (fgetc(in) - 128) * (1.0 / 128.0);
      chan[1][len]= (fgetc(in) - 128) * (1.0 / 128.0);
      chan[2][len]= (fgetc(in) - 128) * (1.0 / 128.0);
      chan[3][len]= (fgetc(in) - 128) * (1.0 / 128.0);
      len++;
   }
   return len;
}

static int 
read_raw(BWFile *ff, BWBlock *bb, FILE *in, float **chan, char *err, int max) {
   int len= 0;
   char *p, *q;
   
   while (len < max) {
      int ch, a, v0;
      p= (char*)ff->read_data;
      for (a= 0; (ch= *p++); a++) {
	 float val;
	 switch (ch) {
	  case '_':	// Dummy byte
	     fgetc(in); a--; continue;
	  case 'b':	// Unsigned byte
	     val= (fgetc(in)-128) * (1.0 / 128); break;
	  case 'w':	// Unsigned 16-bit word, little-endian
	     v0= fgetc(in); v0 += fgetc(in) << 8;
	     val= (v0 - 32768) * (1.0 / 32768); break;
	  case 'W':	// Unsigned 16-bit word, big-endian
	     v0= fgetc(in) << 8; v0 += fgetc(in);
	     val= (v0 - 32768) * (1.0 / 32768); break;
	  case 'c':	// Signed char (8-bit)
	     val= ((fgetc(in)^128)-128) * (1.0 / 128); break;
	  case 's':	// Signed 16-bit word, little-endian
	     v0= fgetc(in); v0 += fgetc(in) << 8;
	     val= ((v0^32768) - 32768) * (1.0 / 32768); break;
	  case 'S':	// Signed 16-bit word, big-endian
	     v0= fgetc(in) << 8; v0 += fgetc(in);
	     val= ((v0^32768) - 32768) * (1.0 / 32768); break;
	  case 'f':	// Machine-format 32-bit float
	     q= (char*)&val; 
	     *q++= fgetc(in); *q++= fgetc(in);
	     *q++= fgetc(in); *q++= fgetc(in);
	     if (isnan(val)) val= 0;
	     break;
	  default:
	     error("Internal error -- unknown raw type '%c'", ch);
	 }
	 chan[a][len]= val;
      }

      len++;
   }
   return len;
}


//
//	Format-specific setup routines
//
//	  okay= setup_*(BWFile *ff, FILE *in, char *fmt, char *arg);
//
//	The format-string is split into two parts 'fmt' and 'arg'.
//	'arg' is the part after the '/', if there is one, or else "".
//	The setup routines are called in order until one of them
//	recognises the 'fmt' part.  If the routine does not recognise
//	'fmt', then it should return 0 immediately.  However, if it
//	knows the format, it should interpret the 'arg' argument if
//	required, and read enough of the file to get past any headers
//	that might be there.  The file-position after this call is
//	saved as the start-position of block 0.  The call should also
//	fill in the following fields, and then finally return 1.
//
//	  ff->read		Read callback routine
//	  ff->read_data		Extra saved info, if required (else leave as 0)
//	  ff->rate		Sample rate in Hz (may be fractional)
//	  ff->chan		Number of channels
//
//	If there are any errors in the format string or arguments
//	'arg', then the routine may call error() to abort the program
//	with an error message.
//

static int 
setup_jm(BWFile *ff, FILE *in, char *fmt, char *arg) {
   char dmy;

   if (0 == strcmp(fmt, "jm2")) {
      ff->read= read_jm2;
      ff->chan= 2;
   } else if (0 == strcmp(fmt, "jm4")) {
      ff->read= read_jm4;
      ff->chan= 4;
   } else return 0;

   if (1 != sscanf(arg, "%lf %c", &ff->rate, &dmy))
      error("Expecting sample rate in format-spec: %s/%s", fmt, arg);

   return 1;
}

static int 
setup_raw(BWFile *ff, FILE *in, char *fmt, char *arg) {
   char *p, dmy, ch, *data;

   if (0 != strcmp(fmt, "raw"))
      return 0;
   
   if (!(p= strchr(arg, ':')))
      error("Expecting <rate>:<fmt> in format spec for 'raw': %s/%s", fmt, arg);
   *p++= 0;

   if (1 != sscanf(arg, "%lf %c", &ff->rate, &dmy))
      error("Expecting sample rate in format-spec: %s/%s:%s", fmt, arg, p);

   data= StrDup(p);
   ff->read_data= data;
   ff->read= read_raw;
   ff->chan= 0;

   while ((ch= *p++)) {
      if (!strchr("_bwWcsSf", ch))
	 error("Invalid character '%c' in raw format-spec: %s/%s:%s", 
	       ch, fmt, arg, data);
      if (ch != '_') ff->chan++;
   }

   return 1;
}
   

//
//	List of setup routines
//

#define NL "\n"
static FormatInfo format_list[]= {
   { setup_jm, 
     "jm2/<rate>         Jim-Meissner file, 2 channels" NL
     "jm4/<rate>         Jim-Meissner file, 4 channels" },
   { setup_raw,
     "raw/<rate>:<fmt>   Raw file, with format given by <fmt> (see docs)" },
   { 0, 0 } 	// Marks end of list
};


// END //

