*
* Copyright (C) DFS Deutsche Flugsicherung (2004, 2005).
* All Rights Reserved.
*
* Acoustic Echo Cancellation NLMS-pw algorithm
*
* Version 0.3 filter created with www.dsptutor.freeuk.com
* Version 0.3.1 Allow change of stability parameter delta
* Version 0.4 Leaky Normalized LMS - pre whitening algorithm
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <math.h>
#include <string.h>
#include <stdint.h>
#include <pulse/xmalloc.h>
#include "adrian-aec.h"
#ifndef DISABLE_ORC
#include "adrian-aec-orc-gen.h"
#endif
#ifdef __SSE__
#include <xmmintrin.h>
#endif
static REAL dotp(REAL a[], REAL b[])
{
REAL sum0 = 0.0f, sum1 = 0.0f;
int j;
for (j = 0; j < NLMS_LEN; j += 2) {
sum0 += a[j] * b[j];
sum1 += a[j + 1] * b[j + 1];
}
return sum0 + sum1;
}
static REAL dotp_sse(REAL a[], REAL b[])
{
#ifdef __SSE__
int j;
REAL sum;
__m128 acc = _mm_setzero_ps();
for (j=0;j<NLMS_LEN;j+=8)
{
acc = _mm_add_ps(acc, _mm_mul_ps(_mm_load_ps(a+j), _mm_loadu_ps(b+j)));
acc = _mm_add_ps(acc, _mm_mul_ps(_mm_load_ps(a+j+4), _mm_loadu_ps(b+j+4)));
}
acc = _mm_add_ps(acc, _mm_movehl_ps(acc, acc));
acc = _mm_add_ss(acc, _mm_shuffle_ps(acc, acc, 0x55));
_mm_store_ss(&sum, acc);
return sum;
#else
return dotp(a, b);
#endif
}
AEC* AEC_init(int RATE, int have_vector)
{
AEC *a = pa_xnew0(AEC, 1);
a->j = NLMS_EXT;
AEC_setambient(a, NoiseFloor);
a->dfast = a->dslow = M75dB_PCM;
a->xfast = a->xslow = M80dB_PCM;
a->gain = 1.0f;
a->Fx = IIR1_init(2000.0f/RATE);
a->Fe = IIR1_init(2000.0f/RATE);
a->cutoff = FIR_HP_300Hz_init();
a->acMic = IIR_HP_init();
a->acSpk = IIR_HP_init();
a->aes_y2 = M0dB;
a->fdwdisplay = -1;
if (have_vector) {
a->w = (REAL *) (((uintptr_t) a->w_arr) - (((uintptr_t) a->w_arr) % 16) + 16);
a->dotp = dotp_sse;
} else {
a->w = a->w_arr;
a->dotp = dotp;
}
return a;
}
void AEC_done(AEC *a) {
pa_assert(a);
pa_xfree(a->Fx);
pa_xfree(a->Fe);
pa_xfree(a->acMic);
pa_xfree(a->acSpk);
pa_xfree(a->cutoff);
pa_xfree(a);
}
static float AEC_dtd(AEC *a, REAL d, REAL x)
{
float ratio, stepsize;
a->dfast += ALPHAFAST * (fabsf(d) - a->dfast);
a->xfast += ALPHAFAST * (fabsf(x) - a->xfast);
a->dslow += ALPHASLOW * (fabsf(d) - a->dslow);
a->xslow += ALPHASLOW * (fabsf(x) - a->xslow);
if (a->xfast < M70dB_PCM) {
return 0.0f;
}
if (a->dfast < M70dB_PCM) {
return 0.0f;
}
ratio = (a->dfast * a->xslow) / (a->dslow * a->xfast);
if (ratio < STEPX1)
stepsize = STEPY1;
else if (ratio > STEPX2)
stepsize = STEPY2;
else
stepsize = STEPY1 + (STEPY2 - STEPY1) * (ratio - STEPX1) / (STEPX2 - STEPX1);
return stepsize;
}
static void AEC_leaky(AEC *a)
{
if (a->xfast >= M70dB_PCM) {
a->hangover = Thold;
} else {
if (a->hangover > 1) {
--(a->hangover);
} else if (1 == a->hangover) {
--(a->hangover);
memset(a->w_arr, 0, sizeof(a->w_arr));
}
}
}
#if 0
void AEC::openwdisplay() {
fdwdisplay = socket_async("127.0.0.1", 50999);
};
#endif
static REAL AEC_nlms_pw(AEC *a, REAL d, REAL x_, float stepsize)
{
REAL e;
REAL ef;
a->x[a->j] = x_;
a->xf[a->j] = IIR1_highpass(a->Fx, x_);
e = d;
if (a->hangover > 0) {
e -= a->dotp(a->w, a->x + a->j);
}
ef = IIR1_highpass(a->Fe, e);
a->dotp_xf_xf += (a->xf[a->j] * a->xf[a->j] - a->xf[a->j + NLMS_LEN - 1] * a->xf[a->j + NLMS_LEN - 1]);
if (stepsize > 0.0f) {
REAL mikro_ef = stepsize * ef / a->dotp_xf_xf;
#ifdef DISABLE_ORC
int i;
for (i = 0; i < NLMS_LEN; i += 2) {
a->w[i] += mikro_ef * a->xf[i + a->j];
a->w[i + 1] += mikro_ef * a->xf[i + a->j + 1];
}
#else
update_tap_weights(a->w, &a->xf[a->j], mikro_ef, NLMS_LEN);
#endif
}
if (--(a->j) < 0) {
a->j = NLMS_EXT;
memmove(a->x + a->j + 1, a->x, (NLMS_LEN - 1) * sizeof(REAL));
memmove(a->xf + a->j + 1, a->xf, (NLMS_LEN - 1) * sizeof(REAL));
}
if (e > MAXPCM) {
return MAXPCM;
} else if (e < -MAXPCM) {
return -MAXPCM;
} else {
return e;
}
}
int AEC_doAEC(AEC *a, int d_, int x_)
{
REAL d = (REAL) d_;
REAL x = (REAL) x_;
d = IIR_HP_highpass(a->acMic, d);
d = FIR_HP_300Hz_highpass(a->cutoff, d);
d *= a->gain;
x = IIR_HP_highpass(a->acSpk, x);
a->stepsize = AEC_dtd(a, d, x);
AEC_leaky(a);
d = AEC_nlms_pw(a, d, x, a->stepsize);
#if 0
if (fdwdisplay >= 0) {
if (++dumpcnt >= (WIDEB*RATE/10)) {
dumpcnt = 0;
write(fdwdisplay, ws, DUMP_LEN*sizeof(float));
memset(ws, 0, sizeof(ws));
} else {
int i;
for (i = 0; i < DUMP_LEN; i += 2) {
ws[i] += w[i];
ws[i + 1] += w[i + 1];
}
}
}
#endif
return (int) d;
}