/* //device/servers/AudioFlinger/AudioBiquadFilter.cpp
**
** Copyright 2009, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/

#include <string.h>
#include <assert.h>

#include "AudioBiquadFilter.h"

#define LIKELY( exp )       (__builtin_expect( (exp) != 0, true  ))
#define UNLIKELY( exp )     (__builtin_expect( (exp) != 0, false ))

namespace android {

const audio_coef_t AudioBiquadFilter::IDENTITY_COEFS[AudioBiquadFilter::NUM_COEFS] = { AUDIO_COEF_ONE, 0, 0, 0, 0 };

AudioBiquadFilter::AudioBiquadFilter(int nChannels, int sampleRate) {
    configure(nChannels, sampleRate);
    reset();
}

void AudioBiquadFilter::configure(int nChannels, int sampleRate) {
    assert(nChannels > 0 && nChannels <= MAX_CHANNELS);
    assert(sampleRate > 0);
    mNumChannels  = nChannels;
    mMaxDelta = static_cast<int64_t>(MAX_DELTA_PER_SEC)
                * AUDIO_COEF_ONE
                / sampleRate;
    clear();
}

void AudioBiquadFilter::reset() {
    memcpy(mCoefs, IDENTITY_COEFS, sizeof(mCoefs));
    mCoefDirtyBits = 0;
    setState(STATE_BYPASS);
}

void AudioBiquadFilter::clear() {
    memset(mDelays, 0, sizeof(mDelays));
}

void AudioBiquadFilter::setCoefs(const audio_coef_t coefs[NUM_COEFS], bool immediate) {
    memcpy(mTargetCoefs, coefs, sizeof(mTargetCoefs));
    if (mState & STATE_ENABLED_MASK) {
        if (UNLIKELY(immediate)) {
            memcpy(mCoefs, coefs, sizeof(mCoefs));
            setState(STATE_NORMAL);
        } else {
            setState(STATE_TRANSITION_TO_NORMAL);
        }
    }
}

void AudioBiquadFilter::process(const audio_sample_t in[], audio_sample_t out[],
                                int frameCount) {
    (this->*mCurProcessFunc)(in, out, frameCount);
}

void AudioBiquadFilter::enable(bool immediate) {
    if (UNLIKELY(immediate)) {
        memcpy(mCoefs, mTargetCoefs, sizeof(mCoefs));
        setState(STATE_NORMAL);
    } else {
        setState(STATE_TRANSITION_TO_NORMAL);
    }
}

void AudioBiquadFilter::disable(bool immediate) {
    if (UNLIKELY(immediate)) {
        memcpy(mCoefs, IDENTITY_COEFS, sizeof(mCoefs));
        setState(STATE_BYPASS);
    } else {
        setState(STATE_TRANSITION_TO_BYPASS);
    }
}

void AudioBiquadFilter::setState(state_t state) {
    switch (state) {
    case STATE_BYPASS:
      mCurProcessFunc = &AudioBiquadFilter::process_bypass;
      break;
    case STATE_TRANSITION_TO_BYPASS:
      if (mNumChannels == 1) {
        mCurProcessFunc = &AudioBiquadFilter::process_transition_bypass_mono;
      } else {
        mCurProcessFunc = &AudioBiquadFilter::process_transition_bypass_multi;
      }
      mCoefDirtyBits = (1 << NUM_COEFS) - 1;
      break;
    case STATE_TRANSITION_TO_NORMAL:
      if (mNumChannels == 1) {
        mCurProcessFunc = &AudioBiquadFilter::process_transition_normal_mono;
      } else {
        mCurProcessFunc = &AudioBiquadFilter::process_transition_normal_multi;
      }
      mCoefDirtyBits = (1 << NUM_COEFS) - 1;
      break;
    case STATE_NORMAL:
      if (mNumChannels == 1) {
        mCurProcessFunc = &AudioBiquadFilter::process_normal_mono;
      } else {
        mCurProcessFunc = &AudioBiquadFilter::process_normal_multi;
      }
      break;
    }
    mState = state;
}

bool AudioBiquadFilter::updateCoefs(const audio_coef_t coefs[NUM_COEFS],
                                    int frameCount) {
    int64_t maxDelta = mMaxDelta * frameCount;
    for (int i = 0; i < NUM_COEFS; ++i) {
        if (mCoefDirtyBits & (1<<i)) {
            audio_coef_t diff = coefs[i] - mCoefs[i];
            if (diff > maxDelta) {
                mCoefs[i] += maxDelta;
            } else if (diff < -maxDelta) {
                mCoefs[i] -= maxDelta;
            } else {
                mCoefs[i] = coefs[i];
                mCoefDirtyBits ^= (1<<i);
            }
        }
    }
    return mCoefDirtyBits == 0;
}

void AudioBiquadFilter::process_bypass(const audio_sample_t * in,
                                       audio_sample_t * out,
                                       int frameCount) {
    // The common case is in-place processing, because this is what the EQ does.
    if (UNLIKELY(in != out)) {
        memcpy(out, in, frameCount * mNumChannels * sizeof(audio_sample_t));
    }
}

void AudioBiquadFilter::process_normal_mono(const audio_sample_t * in,
                                            audio_sample_t * out,
                                            int frameCount) {
    size_t nFrames = frameCount;
    audio_sample_t x1 = mDelays[0][0];
    audio_sample_t x2 = mDelays[0][1];
    audio_sample_t y1 = mDelays[0][2];
    audio_sample_t y2 = mDelays[0][3];
    const audio_coef_t b0 = mCoefs[0];
    const audio_coef_t b1 = mCoefs[1];
    const audio_coef_t b2 = mCoefs[2];
    const audio_coef_t a1 = mCoefs[3];
    const audio_coef_t a2 = mCoefs[4];
    while (nFrames-- > 0) {
        audio_sample_t x0 = *(in++);
        audio_coef_sample_acc_t acc;
        acc = mul_coef_sample(b0, x0);
        acc = mac_coef_sample(b1, x1, acc);
        acc = mac_coef_sample(b2, x2, acc);
        acc = mac_coef_sample(a1, y1, acc);
        acc = mac_coef_sample(a2, y2, acc);
        audio_sample_t y0 = coef_sample_acc_to_sample(acc);
        y2 = y1;
        y1 = y0;
        x2 = x1;
        x1 = x0;
        (*out++) = y0;
    }
    mDelays[0][0] = x1;
    mDelays[0][1] = x2;
    mDelays[0][2] = y1;
    mDelays[0][3] = y2;
}

void AudioBiquadFilter::process_transition_normal_mono(const audio_sample_t * in,
                                                       audio_sample_t * out,
                                                       int frameCount) {
    if (updateCoefs(mTargetCoefs, frameCount)) {
        setState(STATE_NORMAL);
    }
    process_normal_mono(in, out, frameCount);
}

void AudioBiquadFilter::process_transition_bypass_mono(const audio_sample_t * in,
                                                       audio_sample_t * out,
                                                       int frameCount)  {
  if (updateCoefs(IDENTITY_COEFS, frameCount)) {
      setState(STATE_NORMAL);
  }
  process_normal_mono(in, out, frameCount);
}

void AudioBiquadFilter::process_normal_multi(const audio_sample_t * in,
                                             audio_sample_t * out,
                                             int frameCount) {
    const audio_coef_t b0 = mCoefs[0];
    const audio_coef_t b1 = mCoefs[1];
    const audio_coef_t b2 = mCoefs[2];
    const audio_coef_t a1 = mCoefs[3];
    const audio_coef_t a2 = mCoefs[4];
    for (int ch = 0; ch < mNumChannels; ++ch) {
        size_t nFrames = frameCount;
        audio_sample_t x1 = mDelays[ch][0];
        audio_sample_t x2 = mDelays[ch][1];
        audio_sample_t y1 = mDelays[ch][2];
        audio_sample_t y2 = mDelays[ch][3];
        while (nFrames-- > 0) {
            audio_sample_t x0 = *in;
            audio_coef_sample_acc_t acc;
            acc = mul_coef_sample(b0, x0);
            acc = mac_coef_sample(b1, x1, acc);
            acc = mac_coef_sample(b2, x2, acc);
            acc = mac_coef_sample(a1, y1, acc);
            acc = mac_coef_sample(a2, y2, acc);
            audio_sample_t y0 = coef_sample_acc_to_sample(acc);
            y2 = y1;
            y1 = y0;
            x2 = x1;
            x1 = x0;
            *out = y0;
            in += mNumChannels;
            out += mNumChannels;
        }
        mDelays[ch][0] = x1;
        mDelays[ch][1] = x2;
        mDelays[ch][2] = y1;
        mDelays[ch][3] = y2;
        in -= frameCount * mNumChannels - 1;
        out -= frameCount * mNumChannels - 1;
    }
}

void AudioBiquadFilter::process_transition_normal_multi(const audio_sample_t * in,
                                                        audio_sample_t * out,
                                                        int frameCount) {
    if (updateCoefs(mTargetCoefs, frameCount)) {
        setState(STATE_NORMAL);
    }
    process_normal_multi(in, out, frameCount);
}

void AudioBiquadFilter::process_transition_bypass_multi(const audio_sample_t * in,
                                                        audio_sample_t * out,
                                                        int frameCount)  {
    if (updateCoefs(IDENTITY_COEFS, frameCount)) {
        setState(STATE_NORMAL);
    }
    process_normal_multi(in, out, frameCount);
}

}
