/*---------------------------------------------------------------------------*
 *                                   IT++			             *
 *---------------------------------------------------------------------------*
 * Copyright (c) 1995-2001 by Tony Ottosson, Thomas Eriksson, Pål Frenger,   *
 * Tobias Ringström, and Jonas Samuelsson.                                   *
 *                                                                           *
 * Permission to use, copy, modify, and distribute this software and its     *
 * documentation under the terms of the GNU General Public License is hereby *
 * granted. No representations are made about the suitability of this        *
 * software for any purpose. It is provided "as is" without expressed or     *
 * implied warranty. See the GNU General Public License for more details.    *
 *---------------------------------------------------------------------------*/

/*! 
  \file 
  \brief Implementation of audio Audio classes and functions.
  \author Tobias Ringström

  1.5

  2003/05/22 08:55:20
*/

#include "srccode/audiofile.h"
#include "base/machdep.h"
#include "base/scalfunc.h"
#include <sys/types.h>
#include <sys/stat.h>

using std::istream;
using std::ostream;
using std::ifstream;
using std::ofstream;
using std::ios;

namespace itpp {

#ifndef DOXYGEN_SHOULD_SKIP_THIS

#define SND_MAGIC    0x2e736e64

  inline static short double_to_short(double x)
  {
    if (x >= 32767.0)
      return 32767;
    else if (x <= -32768.0)
      return -32768;
    else
      return round_i(x);
  }

  inline static signed char double_to_char(double x)
  {
    if (x >= 127.0)
      return 127;
    else if (x <= -128.0)
      return -128;
    else
      return round_i(x);
  }

#define MK_RW_FUNS(type,namestub)                        \
inline static type read_##namestub(istream &s)           \
{ type v; s.read(reinterpret_cast<char *>(&v), sizeof(type)); return v; }          \
inline static void write_##namestub(ostream &s, type v)  \
{ s.write(reinterpret_cast<char *>(&v), sizeof(type)); }

#endif /* DOXYGEN_SHOULD_SKIP_THIS */

MK_RW_FUNS(signed char,char)
  MK_RW_FUNS(short,short)
  MK_RW_FUNS(int,int)
  MK_RW_FUNS(unsigned,unsigned)
  MK_RW_FUNS(float,float)
  MK_RW_FUNS(double,double)

  bool raw16le_read(const char *fname, vec &v)
{
  ifstream file(fname, ios::in | ios::binary);
  struct stat st;
  int n, i;

  if (stat(fname, &st) == -1)
    return false;

  n = st.st_size / 2; // short vs. byte
  v.set_size(n, false);
  for (i=0; i<n; i++)
    v(i) = little_endian(read_short(file)) / 32768.0;
  if (!file)
    return false;
	
  return true;
}

bool raw16le_read(const char *fname, vec &v, int beg, int len)
{
  ifstream file(fname, ios::in | ios::binary);
  int i;

  it_assert1(len >= 0, "raw16_read()");
  v.set_size(len, false);
  file.seekg(2 * beg);
  for (i=0; i<len; i++)
    v(i) = little_endian(read_short(file)) / 32768.0;
  if (!file)
    return false;
	
  return true;
}

bool raw16le_write(const char *fname, const vec &v, bool append)
{
  ofstream file(fname, (append ? ios::app | ios::ate : ios::out | ios::trunc) | ios::binary);
  int i;

  for (i=0; i<v.size(); i++)
    write_short(file, little_endian(double_to_short(v(i) * 32768.0)));
  if (!file)
    return false;
	
  return true;
}

bool raw16be_read(const char *fname, vec &v)
{
  ifstream file(fname, ios::in | ios::binary);
  struct stat st;
  int n, i;

  if (stat(fname, &st) == -1)
    return false;

  n = st.st_size / 2; // short vs. byte
  v.set_size(n, false);
  for (i=0; i<n; i++)
    v(i) = big_endian(read_short(file)) / 32768.0;
  if (!file)
    return false;
	
  return true;
}

bool raw16be_read(const char *fname, vec &v, int beg, int len)
{
  ifstream file(fname, ios::in | ios::binary);
  int i;

  it_assert1(len >= 0, "raw16_read()");
  v.set_size(len, false);
  file.seekg(2 * beg);
  for (i=0; i<len; i++)
    v(i) = big_endian(read_short(file)) / 32768.0;
  if (!file)
    return false;
	
  return true;
}

bool raw16be_write(const char *fname, const vec &v, bool append)
{
  ofstream file(fname, (append ? ios::app | ios::ate : ios::out | ios::trunc) | ios::binary);
  int i;

  for (i=0; i<v.size(); i++)
    write_short(file, big_endian(double_to_short(v(i) * 32768.0)));
  if (!file)
    return false;
	
  return true;
}

//////////////////////////////////////////////////
//
// Audio_File
//
//////////////////////////////////////////////////
Audio_File::Audio_File()
{
  is_valid = false;
}

//////////////////////////////////////////////////
//
// SND_Format
//
//////////////////////////////////////////////////
int SND_Format::sample_size() const
{
  switch (header.encoding) {
  case enc_mulaw8 :   return 1;
  case enc_alaw8 :    return 1;
  case enc_linear8 :  return 1;
  case enc_linear16 : return 2;
  case enc_linear24 : return 3;
  case enc_linear32 : return 4;
  case enc_float :    return 4;
  case enc_double :   return 8;
  }
  return 0;
}

bool SND_Format::read_header(istream &f)
{
  f.seekg(0);
  header.magic = big_endian(read_unsigned(f));
  header.hdr_size = big_endian(read_unsigned(f));
  header.data_size = big_endian(read_unsigned(f));
  header.encoding = big_endian(read_unsigned(f));
  header.sample_rate = big_endian(read_unsigned(f));
  header.channels = big_endian(read_unsigned(f));
  f.read(header.info, SND_INFO_LEN);
  if (!f || header.magic != SND_MAGIC) {
    std::cerr << header.magic << " != " << SND_MAGIC << std::endl;
    it_warning("SND_Format::read_header(): This is not a .snd file!");
    return false;
  }
  f.seekg(header.hdr_size);

  return f.good();
}

bool SND_Format::write_header(ostream &f)
{
  f.seekp(0);
  header.magic = SND_MAGIC;
  header.hdr_size = sizeof(header);
  memset(header.info, 0, SND_INFO_LEN);

  write_unsigned(f, big_endian(header.magic));
  write_unsigned(f, big_endian(header.hdr_size));
  write_unsigned(f, big_endian(header.data_size));
  write_unsigned(f, big_endian(header.encoding));
  write_unsigned(f, big_endian(header.sample_rate));
  write_unsigned(f, big_endian(header.channels));
  f.write(reinterpret_cast<char *>(&header.info), SND_INFO_LEN);

  return f.good();
}

//////////////////////////////////////////////////
//
// SND_In_File
//
//////////////////////////////////////////////////

SND_In_File::SND_In_File()
{
}

SND_In_File::SND_In_File(const char *fname)
{
  open(fname);
}

bool SND_In_File::open(const char *fname)
{
  if (file.is_open())
    close();
  file.clear();
  is_valid = false;
  file.open(fname, ios::in | ios::binary);
  if (!file)
    return false;
  if (!read_header(file)) {
    file.close();
    return false;
  }

  is_valid = true;
  return true;
}

void SND_In_File::close()
{
  file.close();
  is_valid = false;
}
    
bool SND_In_File::seek_read(int pos)
{
  if (pos < 0)
    file.seekg(0, ios::end);
  else
    file.seekg(header.hdr_size + header.channels * sample_size() * pos);
  return true;
}

int SND_In_File::tell_read()
{
  if (!good())
    return -1;
    
  return ( static_cast<int>(file.tellg()) - sizeof(header) ) / ( header.channels * sample_size() );
}

bool SND_In_File::read(vec &v)
{
  if (!good())
    return false;
    
  int i, n;
    
  n = samples();
  v.set_size(n, false);
  seek_read(0);
    
  switch (header.encoding) {
  case enc_linear8 :
    for (i=0; i<n; i++)
      v(i) = read_char(file) / 128.0;
    break;
  case enc_linear16 :
    for (i=0; i<n; i++)
      v(i) = big_endian(read_short(file)) / 32768.0;
    break;
  case enc_float :
    for (i=0; i<n; i++)
      v(i) = big_endian(read_float(file));
    break;
  case enc_double :
    for (i=0; i<n; i++)
      v(i) = big_endian(read_double(file));
    break;
  default :
    it_warning("SND_In_File::read(): Unsupported encoding!");
    return false;
  }
  return file.good();
}

bool SND_In_File::read(vec &v, int n)
{
  if (!good())
    return false;

  int i;
    
  v.set_size(n, false);
  switch (header.encoding) {
  case enc_linear8 :
    for (i=0; i<n; i++)
      v(i) = read_char(file) / 128.0;
    break;
  case enc_linear16 :
    for (i=0; i<n; i++)
      v(i) = big_endian(read_short(file)) / 32768.0;
    break;
  case enc_float :
    for (i=0; i<n; i++)
      v(i) = big_endian(read_float(file));
    break;
  case enc_double :
    for (i=0; i<n; i++)
      v(i) = big_endian(read_double(file));
    break;
  default :
    it_warning("SND_In_File::read(): Unsupported encoding!");
    return false;
  }
  return file.good();
}

//////////////////////////////////////////////////
//
// SND_Out_File
//
//////////////////////////////////////////////////
SND_Out_File::SND_Out_File()
{
}

SND_Out_File::SND_Out_File(const char *fname, int rate, data_encoding e)
{
  open(fname, rate, e);
}

bool SND_Out_File::open(const char *fname, int rate, data_encoding e)
{
  if (file.is_open())
    close();
  file.clear();
  is_valid = false;
  file.open(fname, ios::out | ios::trunc | ios::binary);
  if (!file)
    return false;
    
  header.data_size = 0;
  header.encoding = (unsigned)e;
  header.sample_rate = rate;
  header.channels = 1;
    
  if (!write_header(file))
    return false;

  is_valid = true;
  return true;
}

void SND_Out_File::close()
{
  file.seekp(0, ios::end);
  header.data_size = static_cast<int>(file.tellp()) - sizeof(header);
  write_header(file);
  file.close();
  is_valid = false;
}
    
bool SND_Out_File::seek_write(int pos)
{
  if (!good())
    return false;

  if (pos < 0)
    file.seekp(0, ios::end);
  else
    file.seekp(sizeof(header) + header.channels * sample_size() * pos);
  return true;
}

int SND_Out_File::tell_write()
{
  if (!good())
    return -1;

  return ( static_cast<int>(file.tellp()) - sizeof(header) ) / ( header.channels * sample_size() );
}

bool SND_Out_File::write(const vec &v)
{
  if (!good())
    return false;

  int i;
    
  switch (header.encoding) {
  case enc_linear8 :
    for (i=0; i<v.size(); i++)
      write_char(file, double_to_char(v(i) * 128.0));
    break;
  case enc_linear16 :
    for (i=0; i<v.size(); i++)
      write_short(file, big_endian(double_to_short(v(i) * 32768.0)));
    break;
  case enc_float :
    for (i=0; i<v.size(); i++)
      write_float(file, big_endian((float)v(i)));
    break;
  case enc_double :
    for (i=0; i<v.size(); i++)
      write_double(file, big_endian((double)v(i)));
    break;
  default :
    it_warning("SND_Out_File::write(): Unsupported encoding!");
    return false;
  }

  return file.good();
}

//////////////////////////////////////////////////
//
// SND_IO_File
//
//////////////////////////////////////////////////
bool SND_IO_File::open(const char *fname)
{
  if (file.is_open())
    close();
  file.clear();
  is_valid = false;
  file.open(fname, ios::in | ios::out | ios::binary);
  if (!file)
    return false;
    
  if (!read_header(file)) {
    file.close();
    return false;
  }

  if (!seek_read(0) || !seek_write(0)) {
    file.close();
    return false;
  }
    
  is_valid = true;
  return true;
}

void SND_IO_File::close()
{
  write_header(file);
  file.close();
  is_valid = false;
}
    
bool snd_read(const char *fname, vec &v)
{
  SND_In_File file;

  if (!file.open(fname))
    return false;

  return file.read(v);
}

bool snd_read(const char *fname, vec &v, int beg, int len)
{
  SND_In_File file;

  if (!file.open(fname))
    return false;

  file.seek_read(beg);
  return file.read(v, len);
}

bool snd_write(const char *fname, const vec &v, int rate, SND_Format::data_encoding e)
{
  SND_Out_File file;

  if (!file.open(fname, rate, e))
    return false;

  return file.write(v);
}

} //namespace itpp


syntax highlighted by Code2HTML, v. 0.9.1