/* aec.cpp
 *
 * Copyright (C) DFS Deutsche Flugsicherung (2004, 2005). 
 * All Rights Reserved.
 *
 * Acoustic Echo Cancellation NLMS-pw algorithm
 *
 * Version 0.3.4 H-infinity LMS-pw algorithm
 * Version 0.3.1 Allow change of stability parameter delta
 * Version 0.3 filter created with www.dsptutor.freeuk.com
 */

#include <stdio.h>

#include <stdlib.h>

#include <string.h>


extern "C" {

	#include <math.h>

	#include "aec_cwrap.h"

	#include <iaxclient.h>

}
//#include "oss.h"


#include "aec.h"

//#include "intercomd.h"


#if 1

/* Vector Dot Product */
REAL dotp(REAL a[], REAL b[])
{
  REAL sum0 = 0.0, sum1 = 0.0;
  int j;

  for (j = 0; j < NLMS_LEN; j += 2) {
    // optimize: partial loop unrolling

    sum0 += a[j] * b[j];
    sum1 += a[j + 1] * b[j + 1];
  }
  return sum0 + sum1;
}

#else


/* this is unfinished work in progress. */
#include <xmmintrin.h>


inline float dotp(float a[], float b[])
{
  int i;
  __m128 xmm0, xmm1, xmm2;
  __m128 xmm3, xmm4, xmm5;
  float sum[8];

  /* XMM convolution - Intel Pentium 3 and above */
  
  sum[0] = sum[1] = sum[2] = sum[3] = 0.0f;
  xmm2 = _mm_load_ps(sum);
    xmm5 = _mm_load_ps(sum);
  for (i = 0; i < NLMS_LEN; i += 8) {
    /* load not aligned data */
    xmm0 = _mm_loadu_ps(&a[i]);    
      xmm3 = _mm_loadu_ps(&a[i+4]);
       
    xmm1 = _mm_loadu_ps(&b[i]);
      xmm4 = _mm_loadu_ps(&b[i+4]);
    
    /* Intel notation: first operand is destination */
    /* GNU as notation: first operand is source */
    xmm0 = _mm_mul_ps(xmm0, xmm1);
      xmm3 = _mm_mul_ps(xmm3, xmm4);
    
    xmm2 = _mm_add_ps(xmm2, xmm0);      
      xmm5 = _mm_add_ps(xmm5, xmm3);
  }
  _mm_store_ps(sum, xmm2);
  _mm_store_ps(&sum[4], xmm5);
 
  return sum[0] + sum[1] + sum[2] + sum[3]
    + sum[4] + sum[5] + sum[6] + sum[7];
}
#endif



AEC::AEC()
{
  max_max_x = 0.0f;
  hangover = 0;
  memset(max_x, 0, sizeof(max_x));
  dtdCnt = dtdNdx = 0;

  max_max_u = 0.0f;
  memset(max_u, 0, sizeof(max_u));
  c_max_u = i_max_u = 0;

  memset(x, 0, sizeof(x));
  memset(xf, 0, sizeof(xf));
  memset(w, 0, sizeof(w));
  j = NLMS_EXT;
  delta = 0.0f;
  setambient(NoiseFloor);
  dfast = dslow = M50dB_PCM;
  xfast = xslow = M40dB_PCM;
  gain = 1.0f;
  Fx.init(2000.0f/RATE);
  Fe.init(2000.0f/RATE);
  Fxx.init(2000.0f/RATE);
  Fdd.init(2000.0f/RATE);
}

inline double AEC::max_dotp_xf_xf(double u)
{
  // optimized implementation of max(u[0], u[1], .., u[L-1]):

  // calculate max of block (DTD_LEN values)

  if (u > max_u[i_max_u]) {
    max_u[i_max_u] = u;
    if (u > max_max_u) {
      max_max_u = u;
      // printf("max_dotp_xf_xf %f\n", sqrt(max_max_u/NLMS_LEN)); 

    }
  }
  if (++c_max_u >= DTD_LEN) {
    c_max_u = 0;
    // calculate max of max

    max_max_u = 0.0f;
    for (int i = 0; i < NLMS_LEN / DTD_LEN; ++i) {
      if (max_u[i] > max_max_u) {
        max_max_u = max_u[i];
      }
    }
    // rotate Ndx

    if (++i_max_u >= NLMS_LEN / DTD_LEN)
      i_max_u = 0;
    max_u[i_max_u] = 0.0f;
  }
  return max_max_u;
}

float ratio;

#if 1

// Adrian soft decision DTD

// (Dual Average Near-End to Far-End signal Ratio DTD)

// This algorithm uses exponential smoothing with differnt

// ageing parameters to get fast and slow near-end and far-end

// signal averages. The ratio of NFRs term

// (dfast / xfast) / (dslow / xslow) is used to compute the stepsize

// A ratio value of 2.5 is mapped to stepsize 0, a ratio of 0 is

// mapped to 1.0 with a limited linear function.

inline float AEC::dtd(REAL d, REAL x)
{
  float stepsize;
  
  d = Fdd.highpass(d);
  x = Fxx.highpass(x);

  // fast near-end and far-end average

  dfast += 2e-3f * (fabsf(d) - dfast);
  xfast += 2e-3f * (fabsf(x) - xfast);
  
  // slow near-end and far-end average

  dslow += 1e-6f * (fabsf(d) - dslow);
  xslow += 1e-6f * (fabsf(x) - xslow);
  
  if (xfast < M70dB_PCM) {
    return 0.0;   // no Spk signal

  }
  
  if (dfast < M70dB_PCM) {
    return 0.0;   // no Mic signal

  }
  
  // ratio of NFRs

  ratio = (dfast * xslow) / (dslow * xfast);
  
  // begrenzte lineare Kennlinie

  const float M = (STEPY2 - STEPY1) / (STEPX2 - STEPX1);
  if (ratio < STEPX1) {
    stepsize = STEPY1;
  } else if (ratio > STEPX2) {
    stepsize = STEPY2;
  } else {
    // Punktrichtungsform einer Geraden

    stepsize = M * (ratio - STEPX1) + STEPY1;
  }

  return stepsize;
}

#else


// Geigel DTD. return is 0.0 or 1.0

inline float AEC::dtd(REAL d, REAL x)
{
  // optimized implementation of max(|x[0]|, |x[1]|, .., |x[L-1]|):

  // calculate max of block (DTD_LEN values)

  x = fabsf(x);
  if (x > max_x[dtdNdx]) {
    max_x[dtdNdx] = x;
    if (x > max_max_x) {
      max_max_x = x;
    }
  }
  if (++dtdCnt >= DTD_LEN) {
    dtdCnt = 0;
    // calculate max of max

    max_max_x = 0.0f;
    for (int i = 0; i < NLMS_LEN / DTD_LEN; ++i) {
      if (max_x[i] > max_max_x) {
        max_max_x = max_x[i];
      }
    }
    // rotate Ndx

    if (++dtdNdx >= NLMS_LEN / DTD_LEN)
      dtdNdx = 0;
    max_x[dtdNdx] = 0.0f;
  }
  // The Geigel DTD algorithm with Hangover timer Thold

  if (fabsf(d) >= GeigelThreshold * max_max_x) {
    hangover = Thold;
  }

  if (hangover)
    --hangover;
   
  // Silence is the same as Double Talk 

  if (max_max_x < M40dB_PCM) {
    // return 0;

  }

  if (hangover > 0) {
    return STEPY2;
  } else {
    return STEPY1;
  }
}

#endif



inline REAL AEC::nlms_pw(REAL d, REAL x_, float stepsize)
{
  x[j] = x_;
  xf[j] = Fx.highpass(x_);     // pre-whitening of x


  // calculate error value 

  // (mic signal - estimated mic signal from spk signal)

  REAL e = d - dotp(w, x + j);
  REAL ef = Fe.highpass(e);     // pre-whitening of e

  
  // optimize: iterative dotp(xf, xf)

  dotp_xf_xf += (xf[j] * xf[j] - xf[j + NLMS_LEN - 1] * xf[j + NLMS_LEN - 1]);
    
  if (stepsize > 0.0) {
    // calculate variable step size

    REAL mikro_ef = stepsize * ef / dotp_xf_xf;
    
    // inspired by H-infinity theory: use max_dotp_xf_xf(dotp_xf_xf)

    // REAL mikro_ef = 1.0f * ef / max_dotp_xf_xf(dotp_xf_xf);


    // update tap weights (filter learning)

    int i;
    for (i = 0; i < NLMS_LEN; i += 2) {
      // optimize: partial loop unrolling

      w[i] += mikro_ef * xf[i + j];
      w[i + 1] += mikro_ef * xf[i + j + 1];
    }
  }

  if (--j < 0) {
    // optimize: decrease number of memory copies

    j = NLMS_EXT;
    memmove(x + j + 1, x, (NLMS_LEN - 1) * sizeof(REAL));
    memmove(xf + j + 1, xf, (NLMS_LEN - 1) * sizeof(REAL));
  }
  
  // Saturation

  if (e > MAXPCM) {
    return MAXPCM;
  } else if (e < -MAXPCM) {
    return -MAXPCM;
  } else {
    return e;
  }
}

// soft decision Acoustic Echo Suppression (AES) or

// Non Linear Processor (NLP): 

// attenuate d for large x signals (like AGC)

inline float AEC::aes(REAL d)
{
  const float X1 = M70dB_PCM, Y1 = M0dB;
  const float X2 = M40dB_PCM, Y2 = M24dB;
  const float M = (Y2 - Y1) / (X2 - X1);

  // begrenzte lineare Kennlinie

  float atten;
  if (xfast < X1) {
    atten = Y1;
  } else if (xfast > X2) {
    atten = Y2;
  } else {
    // Punktrichtungsform einer Geraden

    atten = M * (xfast - X1) + Y1;
  }
  return d * atten;
}


int AEC::doAEC(int d_, int x_, int enable)
{
  extern int noaes;

  REAL d = (REAL) d_;
  REAL x = (REAL) x_;

  // Mic Highpass Filter - to remove DC

  d = acMic.highpass(d);

  // Mic Highpass Filter - cut-off below 300Hz

  d = cutoff.highpass(d);
  
  // Amplify, for e.g. Soundcards with -6dB max. volume

  d *= gain;

  // Spk Highpass Filter - to remove DC

  if (enable) 
    x = acSpk.highpass(x);

  // Double Talk Detector

  float stepsize = dtd(d, x);
  
  // Acoustic Echo Cancellation

  d = nlms_pw(d, x, stepsize);
  
  // Acoustic Echo Suppression

  //if (NO == noaes) {

    d = aes(d);
  //}


  return (int) d;
}


syntax highlighted by Code2HTML, v. 0.9.1