
/*
 * --- GSMP-COPYRIGHT-NOTE-BEGIN ---
 * 
 * This copyright note is auto-generated by ./scripts/Create-CopyPatch.
 * Please add additional copyright information _after_ the line containing
 * the GSMP-COPYRIGHT-NOTE-END tag. Otherwise it might get removed by
 * the ./scripts/Create-CopyPatch script. Do not edit this copyright text!
 * 
 * GSMP: pcm/include/Types.hh
 * General Sound Manipulation Program is Copyright (C) 2000 - 2004
 *   Valentin Ziegler and René Rebe
 * 
 * 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; version 2. A copy of the GNU General
 * Public License can be found in the file LICENSE.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANT-
 * ABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 * 
 * --- GSMP-COPYRIGHT-NOTE-END ---
 */

/* Short Description:
 *   Advanced templates to perform automated type convertion for
 *   various kind of integer and floating point types. Convertions for
 *   the same type are entirely optimized away - and integer convertions
 *   are "normally" optimized down to a single shift instruction - of
 *   course fully inlined.
 */

#ifndef LOWLEVEL__TYPES_HH__
#define LOWLEVEL__TYPES_HH__

#include <sys/types.h>
#include <endian.h>
#include <byteswap.h>

#include <math.h>

#include <limits>

namespace GSMP {
  
  typedef unsigned int local_index;
  typedef u_int64_t global_index;
  typedef int local_delta;
  typedef int64_t global_delta;

  
  typedef int channel_index;
  typedef int sample_rate;

  class EndianessTraits {
  public:
    
    static const bool IsSpecialized = false;
    static const bool HasEndianess = false;
    static const bool IsBigendian = false;
  };

  class LittleEndianessTraits : public EndianessTraits {
  public:
  
    static const bool IsSpecialized = true;
    static const bool HasEndianess = true;
    static const bool IsBigendian = false;
  };

  class BigEndianessTraits : public EndianessTraits {
  public:
  
    static const bool IsSpecialized = true;
    static const bool HasEndianess = true;
    static const bool IsBigendian = true;
  };

#if defined __BIG_ENDIAN__ || defined _BIG_ENDIAN
  typedef BigEndianessTraits HostEndianessTraits;
#else
  typedef LittleEndianessTraits HostEndianessTraits;
#endif
  
  typedef BigEndianessTraits NetworkEndianessTraits;
  
  class InterleaveTraits {
  public:
    static const bool IsSpecialized = false;
  };
    
  class InterleavedTraits : public InterleaveTraits {
  public:
    static const bool IsSpecialized = true;
    static const bool IsInterleaved = true;
  };

  class NonInterleavedTraits : public InterleaveTraits {
  public:
    static const bool IsSpecialized = true;
    static const bool IsInterleaved = false;
  };
  
  template<typename T>
  class PcmTraits {};

  template <> class PcmTraits<int8_t> {
  public:
    typedef int8_t pcm_type;
    typedef int mul_type;

    static const bool IsSpecialized = true;
    static const bool IsInteger = true;
    static const int Bits = 8;
    static const int OverflowBits = 0;
  
    static const pcm_type Min () { return std::numeric_limits<pcm_type>::min(); };
    static const pcm_type Max () { return std::numeric_limits<pcm_type>::max(); };

    static const pcm_type Zero () { return 0; };
    static const pcm_type dBfs () { return std::numeric_limits<pcm_type>::max(); };
  
    static const int Size () { return sizeof (pcm_type); };
  };

  template <> class PcmTraits<int16_t> {
  public:
    typedef int16_t pcm_type;
    typedef int mul_type;
  
    static const bool IsSpecialized = true;
    static const bool IsInteger = true;
    static const int Bits = 16;
    static const int OverflowBits = 0;
  
    static const pcm_type Min () { return std::numeric_limits<pcm_type>::min(); };
    static const pcm_type Max () { return std::numeric_limits<pcm_type>::max(); };

    static const pcm_type Zero () { return 0; };
    static const pcm_type dBfs () { return std::numeric_limits<pcm_type>::max(); };
  
    static const int Size () { return sizeof (pcm_type); };
  };

  template <> class PcmTraits<int32_t> {
  public:
    typedef int32_t pcm_type;
    typedef int64_t mul_type;
  
    static const bool IsSpecialized = true;
    static const bool IsInteger = true;
    static const int Bits = 24;
    static const int OverflowBits = 8;
  
    static const pcm_type Min () { return std::numeric_limits<pcm_type>::min(); };
    static const pcm_type Max () { return std::numeric_limits<pcm_type>::max(); };
  
    static const pcm_type Zero () { return 0; };

    // we use the last 8 high bits as overflow protection
    static const pcm_type dBfs () {
      return std::numeric_limits<pcm_type>::max() >> OverflowBits;
    };
  
    static const int Size () { return sizeof (pcm_type); };
  };

  template <> class PcmTraits<int64_t> {
  public:
    typedef int64_t pcm_type;
    typedef int64_t mul_type;
 
    static const bool IsSpecialized = true;
    static const bool IsInteger = true;
    static const int Bits = 48;
    static const int OverflowBits = 16;
  
    static const pcm_type Min () { return std::numeric_limits<pcm_type>::min(); };
    static const pcm_type Max () { return std::numeric_limits<pcm_type>::max(); };
  
    static const pcm_type Zero () { return 0; };
    // we use the last 16 high bits as overflow protection
    static const pcm_type dBfs () {
      return std::numeric_limits<pcm_type>::max() >> OverflowBits;
    };
  
    static const int Size () { return sizeof (pcm_type); };
  };

  template <> class PcmTraits<float> {
  public:
    typedef float pcm_type;
    typedef float mul_type;
  
    static const bool IsSpecialized = true;
    static const bool IsInteger = false;
    static const int Bits = 32;
    static const int OverflowBits = 0;
  
    static const pcm_type Min () { return std::numeric_limits<pcm_type>::min(); };
    static const pcm_type Max () { return std::numeric_limits<pcm_type>::max(); };
  
    static const pcm_type Zero () { return 0.0; };
    static const pcm_type dBfs () { return 1.0; };
  
    static const int Size () { return sizeof (pcm_type); };
  };

  template <> class PcmTraits<double> {
  public:
    typedef double pcm_type;
    typedef double mul_type;  

    static const bool IsSpecialized = true;
    static const bool IsInteger = false;
    static const int Bits = 64;
    static const int OverflowBits = 0;
  
    static const pcm_type Min () { return std::numeric_limits<pcm_type>::min(); };
    static const pcm_type Max () { return std::numeric_limits<pcm_type>::max(); };
  
    static const pcm_type Zero () { return 0.0; };
    static const pcm_type dBfs () { return 1.0; };
  
    static const int Size () { return sizeof (pcm_type); };
  };


  template <typename E_SRC, typename E_DEST, typename T>
  class PcmByteSwap
  {
  public:
  };

  template <typename E_SRC, typename E_DEST>
  class PcmByteSwap<E_SRC, E_DEST, int8_t>
  {
  public:
    inline static const int8_t Swap (const int8_t& source)
    { return source; };
  };

  template <typename E_SRC, typename E_DEST>
  class PcmByteSwap<E_SRC, E_DEST, int16_t>
  {
  public:
    inline static const int16_t Swap (const int16_t& source) {
      if (E_SRC::IsBigendian == E_DEST::IsBigendian)
	return source;
      else
	return bswap_16 (source);
    };
  };

  template <typename E_SRC, typename E_DEST>
  class PcmByteSwap<E_SRC, E_DEST, int32_t>
  {
  public:
    inline static const int32_t Swap (const int32_t& source) {
      if (E_SRC::IsBigendian == E_DEST::IsBigendian)
	return source;
      else
	return bswap_32 (source);
    };
  };

  template <typename E_SRC, typename E_DEST>
  class PcmByteSwap<E_SRC, E_DEST, int64_t>
  {
  public:
    inline static const int64_t Swap (const int64_t& source) {
      if (E_SRC::IsBigendian == E_DEST::IsBigendian)
	return source;
      else
	return bswap_64 (source);
    };
  };

  template <typename E_SRC, typename E_DEST>
  class PcmByteSwap<E_SRC, E_DEST, float>
  {
  public:
    inline static const float Swap (const float& source) {
      return source;
    };
  };

  template <typename E_SRC, typename E_DEST>
  class PcmByteSwap<E_SRC, E_DEST, double>
  {
  public:
    inline static const double Swap (const double& source) {
      return source;
    };
  };


  template <typename T_SRC, typename T_DEST,
	    typename E_SRC = HostEndianessTraits,
	    typename E_DEST = HostEndianessTraits>
  class PcmConvert
  {
  public:
    inline static T_DEST Convert (T_SRC source);
  };

  template <typename T_SRC, typename T_DEST,
	    typename E_SRC,
	    typename E_DEST>
  T_DEST PcmConvert<T_SRC, T_DEST, E_SRC, E_DEST>::Convert (T_SRC source)
  {
    // optimize if the dBfs value is the same
    if (PcmTraits<T_DEST>::dBfs() == PcmTraits<T_SRC>::dBfs() &&
	E_DEST::IsBigendian == E_SRC::IsBigendian)
      return (T_DEST) source;

    T_SRC src_val;
    // maybe byteswap
    src_val = PcmByteSwap<E_SRC, HostEndianessTraits, T_SRC>::Swap (source);

    // optimize if the types are both integer and we can shift
    if (PcmTraits<T_SRC>::IsInteger && PcmTraits<T_DEST>::IsInteger)
      {
	const int64_t one = 1;  
	T_DEST dest_val;

	int TsrcBits = PcmTraits<T_SRC>::Bits;
	int TdestBits = PcmTraits<T_DEST>::Bits;

	// WARNING: The bit-shift operators are implementation dependant
	//          and do not garantee to shift the sign ...
	
	// WARNING EYE CANCER: this ugly * and / with (1 << bit-count)
	//         is due to the naive template instanciation trying to
	//         generate the code for floating point types ...
	if (TdestBits > TsrcBits) {
	  dest_val = ((T_DEST) source) * (one << (TdestBits - TsrcBits));
	}
	else {
	  T_SRC t_dest_val = source / (one << (TsrcBits - TdestBits));
	  
	  // we need to check for overflows ...
	  if (t_dest_val > PcmTraits<T_DEST>::Max () )
	    t_dest_val = (T_SRC) PcmTraits<T_DEST>::Max ();
	  else if (t_dest_val < PcmTraits<T_DEST>::Min () )
	    t_dest_val = (T_SRC) PcmTraits<T_DEST>::Min ();

	  dest_val = (T_DEST) t_dest_val;
	}

	// maybe byteswap
	return PcmByteSwap<HostEndianessTraits, E_DEST, T_DEST>::Swap (dest_val);
      }

    // else the generic case to scale using dBfs ...
    return (T_DEST) (PcmTraits<T_DEST>::dBfs () / PcmTraits<T_SRC>::dBfs () * src_val);
  };

  template <typename T_DATA>
  class PcmArithmetic
  {
  public:
    typedef PcmTraits <T_DATA> PcmTraits;
    typedef PcmConvert <double, T_DATA, HostEndianessTraits, HostEndianessTraits> ConvertDouble;
    typedef typename PcmTraits::mul_type mul_type;

    const static bool is_specialized = false;

    static inline T_DATA Prod (const T_DATA& a, const T_DATA& b)
    { // TODO: Overflow Checking ?
      const bool IsInteger = PcmTraits::IsInteger;
      const int DataBits = PcmTraits::Bits;
      const int OverflowBits = PcmTraits::OverflowBits;
      const int TotalBits = DataBits + OverflowBits;


      if (IsInteger)
	{
	  if (TotalBits > 32)
	    { // Pre-Op shift the operants for large integer fixed point arithmetic
	      const int64_t one = 1;
	      mul_type ma = ((mul_type) a) / (one << ((DataBits/2)-1));
	      mul_type p = ((mul_type) b) / (one << (DataBits/2));
	      return p * ma;
	    }
	  else
	    {
	      mul_type p = (mul_type) b * a;

	      // Post-Op shift the result for fixed point arithmetic
	      const int64_t one = 1;
	      p = p / (one << (DataBits - 1));
	      return (T_DATA) p;
	    }
	}
      else
	return a * b;
    }

    // Multiplication for Quantity-Relation evaluation
    // shifts fixed point operants more than necessary to gain more
    // space for overflow and uses faster multiplication on T_DATA
    static inline T_DATA LoProd (const T_DATA& a, const T_DATA& b)
    {
      const bool IsInteger = PcmTraits::IsInteger;
      const int DataBits = PcmTraits::Bits;

      if (IsInteger)
	{ 
	  const int64_t one = 1;
	  T_DATA ma = ((mul_type) a) / (one << ((DataBits/2)+2));
	  T_DATA p = ((mul_type) b) / (one << ((DataBits/2)+2));
	  return p * ma;
	}
      else
	return a * b;
    };

    static inline const T_DATA& Scale (T_DATA& x, const T_DATA& factor)
    {
      x = Prod (x, factor);
      return x;
    }

    static inline T_DATA Sum (const T_DATA& a, const T_DATA& b)
    { // TODO: Overflow Checking ?
      return a + b;
    }

    static inline const T_DATA& Add (T_DATA& x, const T_DATA& increase)
    {
      x = Sum (x, increase);
      return x;
    }

    static inline const T_DATA& Macc (T_DATA& x, const T_DATA& op, const T_DATA& factor)
    {
      return Add (x, Prod (op, factor));
    }

    // Macc operation using LoProd
    static inline const T_DATA& LoMacc  (T_DATA& x, const T_DATA& op, const T_DATA& factor)
    {
      return Add (x, LoProd (op, factor));
    }

    static inline T_DATA Fraction (const T_DATA& x, const unsigned int& divisor)
    {
      return x / ((T_DATA) divisor);
    };

    static inline T_DATA Inv (const T_DATA& x)
    {
      return PcmTraits::Zero () - x;
    }

    static inline T_DATA Weighted (const T_DATA& x, const T_DATA& y, const double& w)
    { // w must be in [0.0, 1.0] !
      return Sum (Prod (x, ConvertDouble::Convert (1.0-w)), Prod (y, ConvertDouble::Convert ((w))));
    }


#ifdef GSMP_SINUS // Testing only. This is still too slow..


    static T_DATA Sinus (const double& phase)
    { // phase must be in [-M_PI, M_PI] !!
      T_DATA x;
      T_DATA phase_sq;

      x = PcmConvert <double, T_DATA, HostEndianessTraits, HostEndianessTraits>::Convert (phase);
      phase_sq = Prod (x, x);

      T_DATA result = x;

      const T_DATA to_3fak = PcmTraits::dBfs () / (T_DATA) (-2*3);
      const T_DATA to_5fak = PcmTraits::dBfs () / (T_DATA) (-4*5);
      const T_DATA to_7fak = PcmTraits::dBfs () / (T_DATA) (-6*7);
      const T_DATA to_9fak = PcmTraits::dBfs () / (T_DATA) (-8*9);
      const T_DATA to_11fak = PcmTraits::dBfs () / (T_DATA) (-10*11);

      Scale (x, to_3fak);
      Add(result, Scale (x, phase_sq));
      Scale (x, to_5fak);
      Add(result, Scale (x, phase_sq));
      Scale (x, to_7fak);
      Add(result, Scale (x, phase_sq));
      Scale (x, to_9fak);
      Add(result, Scale (x, phase_sq));
      Scale (x, to_11fak);
      Add(result, Scale (x, phase_sq));

      return result;
    }
  };

#else

  static T_DATA Sinus (const double& phase)
    { // phase should be in [-M_PI, M_PI] !!
      return ConvertDouble::Convert (sin (phase));
    }
  };


#endif

} // namespace GSMP

  /* include template implementation */
#define LOWLEVEL__TYPES_TMPL__
#include "template/Types.tcc"
#undef LOWLEVEL__TYPES_TMPL__

#endif // LOWLEVEL__TYPES_HH__
