// file: $isip/class/mmedia/AudioFile/adf_08.cc
// version: $Id: adf_08.cc 9286 2003-08-05 19:46:21Z parihar $
//

// isip include files
//
#include "AudioFile.h"

// method: getData
//
// arguments: none
//  Vector<VectorFloat>& data: (output) the audio data
//  long start_samp: (input) the start sample of the audio data
//  long num_samp: (input) the number of samples
//  
// return: number of samples read
//
// this method gets the filtered data, if they are already in the circular
// buffer, get them directly, or else get them from the audio file and filter
// them
//
long AudioFile::getData(Vector<VectorFloat>& data_a, long start_samp_a,
			long num_samp_a) {

  // loop over each channel
  //
  long num_read = 0;

  // file is binary format
  //
  for (long ctag = 0; ctag < num_channels_d; ctag++) {
    
    long n = getData(data_a(ctag), ctag, start_samp_a, num_samp_a);

    // error check
    //
    if (n < 0) {
      return -1;
    }
    num_read += n;
  }
  
  // exit gracefully
  //
  return num_read;
}

// method: getData
//
// arguments: none
//  VectorFloat& data: (output) the audio data
//  long ctag: (input) the channel to read
//  long start_samp: (input) the start sample of the audio data
//  long num_samp: (input) the duration of the audio data
//  
// return: number of samples read
//
// this method gets the filtered data, if they are already in the circular
// buffer, get them directly, or else get them from the audio file and filter
// them
//
long AudioFile::getData(VectorFloat& data_a, long ctag_a,
			long start_samp_a, long num_samp_a) {
  
  // check argument
  //
  if (ctag_a < 0) {
    Error::handle(name(), L"getData", Error::ARG, __FILE__, __LINE__);
    return -1;
  }

  // bad arguments
  //
  if (num_samp_a < 0) {
    Error::handle(name(), L"getData", Error::ARG, __FILE__, __LINE__);
    return -1;
  }

  // this is a common loop termination condition
  //
  else if (num_samp_a == 0) {
    data_a.clear();
    return -1;
  }
  
  long output_index = 0;
  
  // determine the maximum sample in the file
  //
  long total_nsamp = getNumSamples();

  // possibly clip the number of samples based on file length
  //
  if ((start_samp_a + num_samp_a) > total_nsamp) {
    num_samp_a = total_nsamp - start_samp_a;
    
    if (debug_level_d >= Integral::DETAILED) {
      String output;
      output.assign(num_samp_a);
      output.insert(L"getData: clipping nsamp to ", 0);
      Console::put(output);
    }
  }

  if (num_samp_a <= 0) {
    data_a.setLength(0);
    return 0;
  }

  // calculate the total number of bytes to read
  //
  data_a.setLength(num_samp_a);

  // get the start and end time of the data already in circular buffer
  //
  long buf_duration = buffers_d(ctag_a).getNumElements();
  long buf_end = buf_end_samp_d(ctag_a);
  long buf_start = getStartSamp(ctag_a);

  long buf_max_sample = buf_end 
    + (((long)buf_size_d - 1)
       * (block_size_d / (sample_num_bytes_d * num_channels_d)));
  
  // do we need samples that start before the circular buffer starts?
  //
  // or is the start sample much farther in the future, such that the
  // current samples would be pushed out before we get there? 
  //
  // if so we need to reset everything and start over.
  //
  if ((start_samp_a < buf_start) || (start_samp_a > buf_max_sample)) {

    if (debug_level_d >= Integral::BRIEF) {
      String output(L"clearing bufs...");
      Console::put(output);
    }
    
    // clear out buffer
    //
    resetBuffer(ctag_a);
    
    // determine the new starting point that lines up on a block boundary
    //
    buf_start = blockFloor(start_samp_a);
    buf_duration = 0;

    // set pointers and time indices
    //
    buf_end = buf_start - 1;
    
    for (long x = 0; x < buf_end_samp_d.length(); x++) {
      buf_end_samp_d(x) = buf_end;
    }
  }

  
  // do we need to read ?
  //
  while (!end_of_file_d &&
	 ((start_samp_a + num_samp_a - 1) > getEndSamp(ctag_a))) {

    if (debug_level_d >= Integral::DETAILED) {
      String output;
      String numeric;
      
      output.assign(L"we need to read since ");
      numeric.assign(start_samp_a);
      output.concat(numeric);
      output.concat(L"+");
      numeric.assign(num_samp_a);
      output.concat(numeric);
      output.concat(L"=");
      numeric.assign(start_samp_a + num_samp_a);
      output.concat(numeric);
      output.concat(L" > ");
      numeric.assign(getEndSamp(ctag_a));
      output.concat(numeric);
      Console::put(output);
    }
      
    // pull in the next block of data
    //
    appendData();

    if (debug_level_d >= Integral::ALL) {
      String output;
      output.assign(getEndSamp(ctag_a));
      output.insert(L"now end_samp = ", 0);
      Console::put(output);
    }
    
    // recalculate boundaries
    //
    buf_duration = buffers_d(ctag_a).getNumElements();
    buf_start = getStartSamp(ctag_a);
    
    // copy over the part of the user's vector we can
    //
    if ((start_samp_a + output_index) >= buf_start) {

      // we now give the user either:
      //   the number of bytes they asked for, or
      //   the all the bytes in the buffer if not all are available
      //
      // last_samp itself will not be read in
      //
      Long last_samp;
      last_samp.min((long)buf_end_samp_d(ctag_a) + 1,
		    start_samp_a + num_samp_a);

      // the absolute reference we keep as the buf_end_samp_d is the
      // last valid element, represented by the zeroeth element in the
      // buffer.  everything valid is non-positive.
      //
      long buf_index = start_samp_a + output_index
	- buf_end_samp_d(ctag_a);

      while ((output_index + start_samp_a) < last_samp) {
	data_a(output_index++) = buffers_d(ctag_a)(buf_index++);
      }
    }
  }

  // possibly pull data from the circular buffer
  //
  if (output_index < num_samp_a) {

    // make sure the data is in the buffer
    //
    buf_duration = buffers_d(ctag_a).getNumElements();
    buf_start = getStartSamp(ctag_a);

    if ((start_samp_a + output_index) < buf_start) {
      Error::handle(name(), L"getData", ERR, __FILE__, __LINE__);
      return -1;
    }
    if ((start_samp_a + num_samp_a - 1) > buf_end_samp_d(ctag_a)) {

      buf_end_samp_d.debug(L"buf_end_samp");
      Error::handle(name(), L"getData", ERR, __FILE__, __LINE__);
      return -1;
    }
    
    // copy over data from the circular buffer to the output vector
    //
    long buf_index = start_samp_a + output_index
      - buf_end_samp_d(ctag_a);
    
    while ((output_index) < (num_samp_a)) {
      data_a(output_index++) = buffers_d(ctag_a)(buf_index++);
    }
  }
  
  // exit gracefully
  //
  return num_samp_a;
}

// method: blockFloor
//
// arguments:
//  long samp_num: (input) exact sample number
//
// return: a rounded sample number (floored by block boundaries)
//
// this method gets the begining sample number of the block this
// sample belongs to
//
long AudioFile::blockFloor(long samp_num_a) const {

  long samples_per_block = block_size_d / sample_num_bytes_d;

  long i = 0;
  for (; i < samp_num_a; i += samples_per_block);
  if (i > 0) {
    i -= samples_per_block;
  }

  return i;
}

// method: blockCeil
//
// arguments:
//  long samp_num: (input) exact sample number
//
// return: a rounded sample number 
//
// this method gets the starting sample number of the next block
//
long AudioFile::blockCeil(long samp_num_a) const {

  long samples_per_block = block_size_d / sample_num_bytes_d;

  long i = 0;
  for (; i < samp_num_a; i += samples_per_block);
  if (i == 0) {
    i += samples_per_block;
  }

  return i;
}

// method: appendData
//
// arguments: none
//  
// return: a boolean value indicating status
//
// this method reads the next block of data from the audiofile and places
// it on the circular buffer
//
boolean AudioFile::appendData() {

  if (end_of_file_d) {
    return false;
  }
  
  // determine the current location of the buffer
  //
  long first_samp = (long)buf_end_samp_d(0) + 1;
  long block_samp = block_size_d / (sample_num_bytes_d * num_channels_d);
  
  for (long ctag = 0; ctag < num_channels_d; ctag++) {
    
    // determine if we are about to overwrite data
    //
    long num_bytes_left = (buffers_d(ctag).getCapacity() -
			   buffers_d(ctag).getNumElements()) * sample_num_bytes_d;

    if (num_bytes_left < (block_size_d / num_channels_d)) {
      
      if (debug_level_d >= Integral::DETAILED) {
	String output(L"Channel ");
	output.concat(ctag);
	output.concat(L": num_bytes_left = ");
	output.concat(num_bytes_left);
	output.concat(L"; clearing out old block");
	Console::put(output);
      }
      
      // clear out the oldest block by advancing the read pointer
      //
      buffers_d(ctag).setRead(block_samp);
    }
  }
  
  // read a block of data
  //
  Vector<VectorFloat> raw_data;
  
  // compute time boundaries for the next block
  //
  
  // channel is ignored, it gives you all of them
  //
  long nsamp = readAudioData(raw_data, -1, first_samp, block_samp);

  if (debug_level_d >= Integral::DETAILED) {
    String numeric;
    String output(L"read ");
    numeric.assign(nsamp);
    output.concat(numeric);
    output.concat(L" samples, ending at file position ");
    numeric.assign(tell());
    output.concat(numeric);
    Console::put(output);
  }

  if (nsamp != block_samp) {

    if (debug_level_d >= Integral::DETAILED) {
      String numeric;
      String output(L"end of file at sample ");
      numeric.assign(nsamp + first_samp);
      output.concat(numeric);
      Console::put(output);
    }
    end_of_file_d = true;
  }

  for (long ctag = 0; ctag < num_channels_d; ctag++) {
    
    for (long i = 0; i < nsamp; i++) {

      // insert the input data into ma filter memory
      //
      ma_mem_d(ctag).append(raw_data(ctag)(i));
      ma_mem_d(ctag).setRead(1);
      if (ma_mem_d(ctag).getNumElements() > 1) {
	ma_mem_d(ctag).seekCurr(1);
      }
      
      Float sum = 0.0;
      
      // handle ma coefficients
      //
      for (long i = 0; i < ma_coeff_d.length(); i++) {
	sum += ma_coeff_d(i) * ma_mem_d(ctag)(-i);
      }

      // handle ar coefficients with non-zero lag (note that read has
      // not yet been updated yet, so ar[0] is corresponds to the same
      // time as ma[-1].
      //
      for (long i = 0; i < (ar_coeff_d.length() - 1); i++) {
	sum += ar_coeff_d(i + 1) * ar_mem_d(ctag)(-i);
      }
      
      // handle the ar[0] term as output gain, note:
      //
      //  y[n] = x[n] + 0.9 * y[n];
      //
      //  (1 - 0.9) * y[n] = x[n];
      //
      //  y[n] = x[n] / (1 - 0.9)
      //
      // therefore, sum = sum / (1 - ar[0])
      //
      // update: for now we enforce that ar[0] must be 1, still waiting
      // on mathematics to clear it up.
      //
      // sum /= (1 - ar_coeff_d(0));
      
      // insert the output into both the data circular buffer and the ar
      // memory.
      //
      ar_mem_d(ctag).append(sum);
      ar_mem_d(ctag).setRead(1);
      if (ar_mem_d(ctag).getNumElements() > 1) {
	ar_mem_d(ctag).seekCurr(1);
      }
      
      buffers_d(ctag).append(sum);

      // only seek if we can
      //
      if (buffers_d(ctag).getNumElements() > 1) {
	buffers_d(ctag).seekCurr(1);
      }
      
      buf_end_samp_d(ctag) += 1;
    }
  }

  // exit gracefully
  //
  return true;
}

// method: getNumSamples
//
// arguments: none
//
// return: the long value of the number of samples
//
// this method gets the number of samples
//
long AudioFile::getNumSamples() const {

  long total_nsamp = -1; 

  // format: RAW
  //
  if (file_format_d == RAW) {

    String output;
    String values;
    long lines = 0;
    // save the current file position and seek end
    //
    long cur_file_pos = tell();
    
    if (file_type_d == TEXT) {
      const_cast<AudioFile*>(this)->rewind();
      while (!eof()) {
	lines++;
	values.assign(lines);
	values.concat(L": ");
	const_cast<AudioFile*>(this)->get(output);
	values.concat(output);
	//	Console::put(values);
      }
      
      const_cast<AudioFile*>(this)->rewind();

      total_nsamp = lines / num_channels_d;
      const_cast<AudioFile*>(this)->seek(cur_file_pos, POS);
    }

    else {
      // seek end
      //
      const_cast<AudioFile*>(this)->seek(0, POS_PLUS_END);
      long total_len = tell();
      
      // compute the total number of samples
      //
      long bytes_per_samp = (sample_num_bytes_d * num_channels_d);
      total_nsamp = total_len / bytes_per_samp;
      
      // resume the file position
      //
      const_cast<AudioFile*>(this)->seek(cur_file_pos, POS);
    }
  }

  // format: SOF
  //
  else if (file_format_d == SOF) {

    if (sof_d == (Sof*)NULL) {
      return Error::handle(name(), L"getNumSamples", Error::MEM,
			   __FILE__, __LINE__);
    }

    if (sof_d->isBinary()) {
      sof_d->seek(sof_length_pos_d, File::POS);
      Long len;
      len.readData(*sof_d, PARAM_DATA);
      total_nsamp = (long)len / (long)num_channels_d;
    }
    else {
      long len = sof_d->getVecParser().countTokens(SofParser::implicitPname());
      total_nsamp = len / (long)num_channels_d;
    }
  }

#ifdef HAVE_SPHERE
  // format: SPHERE
  //
  else if (file_format_d == SPHERE) {

    if (sp_file_d == (SP_FILE*)NULL) {
      return Error::handle(name(), L"getNumSamples", Error::MEM,
			   __FILE__, __LINE__);
    }
    if (sp_h_get_field(sp_file_d, "sample_count", T_INTEGER,
		       (void**)&total_nsamp)> (long)0) {
      return Error::handle(name(), L"getNumSamples", Error::READ,
			   __FILE__, __LINE__);
    }
  }
#endif

#ifdef HAVE_AUDIOFILE
  // format: supported by SGI's audiofile library
  //
 else {
   if (af_file_d == (AFfilehandle)NULL) {
     return Error::handle(name(), L"getNumSamples", Error::MEM,
			  __FILE__, __LINE__);
   }
   
    total_nsamp = afGetFrameCount(af_file_d, AF_DEFAULT_TRACK);
  }
#endif
  
  // return the number of samples
  //
  return total_nsamp;
}

// method: readAudioData
//
// arguments:
//  Vector<VectorFloat>& data: (output) the audio data
//  long ctag: (input) the channel to read
//  long start_samp: (input) the start time of the audio data
//  long num_samp: (input) the end time of the audio data
//  
// return: number of samples read
//
// this method gets data from the audio file and put each channel data into a
// VectorFloat
//
long AudioFile::readAudioData(Vector<VectorFloat>& data_a,
			      long ctag_a, long start_samp_a,
			      long num_samp_a) {
  
  // make sure the file is open
  //
  if (!isOpen()) {
    return Error::handle(name(), L"", ERR_NOTOPN, __FILE__, __LINE__);
  }
  
  // for binary read
  //
  if (file_format_d == RAW) {
    return readRawData(data_a, ctag_a, start_samp_a, num_samp_a);
  }
  
  // sof file
  //
  else if (file_format_d == SOF) {
    return readSofData(data_a, ctag_a, start_samp_a, num_samp_a);
  }
  
  // for sphere file
  //
  else if (file_format_d == SPHERE) {
    return readSphereData(data_a, ctag_a, start_samp_a, num_samp_a);
  }  
  
  // file types supported by sgi's audiofile library
  //
  else {
    return readAFData(data_a, ctag_a, start_samp_a, num_samp_a);
  }
  
  // exit greacefully
  //
  return true;
}
