/*
 * 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.
 */

#define LOG_TAG "AudioEqualizer"

#include <assert.h>
#include <stdlib.h>
#include <new>
#include <utils/Log.h>

#include "AudioEqualizer.h"
#include "AudioPeakingFilter.h"
#include "AudioShelvingFilter.h"
#include "EffectsMath.h"

namespace android {

size_t AudioEqualizer::GetInstanceSize(int nBands) {
    assert(nBands >= 2);
    return sizeof(AudioEqualizer) +
           sizeof(AudioShelvingFilter) * 2 +
           sizeof(AudioPeakingFilter) * (nBands - 2);
}

AudioEqualizer * AudioEqualizer::CreateInstance(void * pMem, int nBands,
                                                int nChannels, int sampleRate,
                                                const PresetConfig * presets,
                                                int nPresets) {
    ALOGV("AudioEqualizer::CreateInstance(pMem=%p, nBands=%d, nChannels=%d, "
         "sampleRate=%d, nPresets=%d)",
         pMem, nBands, nChannels, sampleRate, nPresets);
    assert(nBands >= 2);
    bool ownMem = false;
    if (pMem == NULL) {
        pMem = malloc(GetInstanceSize(nBands));
        if (pMem == NULL) {
            return NULL;
        }
        ownMem = true;
    }
    return new (pMem) AudioEqualizer(pMem, nBands, nChannels, sampleRate,
                                     ownMem, presets, nPresets);
}

void AudioEqualizer::configure(int nChannels, int sampleRate) {
    ALOGV("AudioEqualizer::configure(nChannels=%d, sampleRate=%d)", nChannels,
         sampleRate);
    mpLowShelf->configure(nChannels, sampleRate);
    for (int i = 0; i < mNumPeaking; ++i) {
        mpPeakingFilters[i].configure(nChannels, sampleRate);
    }
    mpHighShelf->configure(nChannels, sampleRate);
}

void AudioEqualizer::clear() {
    ALOGV("AudioEqualizer::clear()");
    mpLowShelf->clear();
    for (int i = 0; i < mNumPeaking; ++i) {
        mpPeakingFilters[i].clear();
    }
    mpHighShelf->clear();
}

void AudioEqualizer::free() {
    ALOGV("AudioEqualizer::free()");
    if (mpMem != NULL) {
        ::free(mpMem);
    }
}

void AudioEqualizer::reset() {
    ALOGV("AudioEqualizer::reset()");
    const int32_t bottom = Effects_log2(kMinFreq);
    const int32_t top = Effects_log2(mSampleRate * 500);
    const int32_t jump = (top - bottom) / (mNumPeaking + 2);
    int32_t centerFreq = bottom + jump/2;

    mpLowShelf->reset();
    mpLowShelf->setFrequency(Effects_exp2(centerFreq));
    centerFreq += jump;
    for (int i = 0; i < mNumPeaking; ++i) {
        mpPeakingFilters[i].reset();
        mpPeakingFilters[i].setFrequency(Effects_exp2(centerFreq));
        centerFreq += jump;
    }
    mpHighShelf->reset();
    mpHighShelf->setFrequency(Effects_exp2(centerFreq));
    commit(true);
    mCurPreset = PRESET_CUSTOM;
}

void AudioEqualizer::setGain(int band, int32_t millibel) {
    ALOGV("AudioEqualizer::setGain(band=%d, millibel=%d)", band, millibel);
    assert(band >= 0 && band < mNumPeaking + 2);
    if (band == 0) {
        mpLowShelf->setGain(millibel);
    } else if (band == mNumPeaking + 1) {
        mpHighShelf->setGain(millibel);
    } else {
        mpPeakingFilters[band - 1].setGain(millibel);
    }
    mCurPreset = PRESET_CUSTOM;
}

void AudioEqualizer::setFrequency(int band, uint32_t millihertz) {
    ALOGV("AudioEqualizer::setFrequency(band=%d, millihertz=%d)", band,
         millihertz);
    assert(band >= 0 && band < mNumPeaking + 2);
    if (band == 0) {
        mpLowShelf->setFrequency(millihertz);
    } else if (band == mNumPeaking + 1) {
        mpHighShelf->setFrequency(millihertz);
    } else {
        mpPeakingFilters[band - 1].setFrequency(millihertz);
    }
    mCurPreset = PRESET_CUSTOM;
}

void AudioEqualizer::setBandwidth(int band, uint32_t cents) {
    ALOGV("AudioEqualizer::setBandwidth(band=%d, cents=%d)", band, cents);
    assert(band >= 0 && band < mNumPeaking + 2);
    if (band > 0 && band < mNumPeaking + 1) {
        mpPeakingFilters[band - 1].setBandwidth(cents);
        mCurPreset = PRESET_CUSTOM;
    }
}

int32_t AudioEqualizer::getGain(int band) const {
    assert(band >= 0 && band < mNumPeaking + 2);
    if (band == 0) {
        return mpLowShelf->getGain();
    } else if (band == mNumPeaking + 1) {
        return mpHighShelf->getGain();
    } else {
        return mpPeakingFilters[band - 1].getGain();
    }
}

uint32_t AudioEqualizer::getFrequency(int band) const {
    assert(band >= 0 && band < mNumPeaking + 2);
    if (band == 0) {
        return mpLowShelf->getFrequency();
    } else if (band == mNumPeaking + 1) {
        return mpHighShelf->getFrequency();
    } else {
        return mpPeakingFilters[band - 1].getFrequency();
    }
}

uint32_t AudioEqualizer::getBandwidth(int band) const {
    assert(band >= 0 && band < mNumPeaking + 2);
    if (band == 0 || band == mNumPeaking + 1) {
        return 0;
    } else {
        return mpPeakingFilters[band - 1].getBandwidth();
    }
}

void AudioEqualizer::getBandRange(int band, uint32_t & low,
                                  uint32_t & high) const {
    assert(band >= 0 && band < mNumPeaking + 2);
    if (band == 0) {
        low = 0;
        high = mpLowShelf->getFrequency();
    } else if (band == mNumPeaking + 1) {
        low = mpHighShelf->getFrequency();
        high = mSampleRate * 500;
    } else {
        mpPeakingFilters[band - 1].getBandRange(low, high);
    }
}

const char * AudioEqualizer::getPresetName(int preset) const {
    assert(preset < mNumPresets && preset >= PRESET_CUSTOM);
    if (preset == PRESET_CUSTOM) {
        return "Custom";
    } else {
        return mpPresets[preset].name;
    }
}

int AudioEqualizer::getNumPresets() const {
    return mNumPresets;
}

int AudioEqualizer::getPreset() const {
    return mCurPreset;
}

void AudioEqualizer::setPreset(int preset) {
    ALOGV("AudioEqualizer::setPreset(preset=%d)", preset);
    assert(preset < mNumPresets && preset >= 0);
    const PresetConfig &presetCfg = mpPresets[preset];
    for (int band = 0; band < (mNumPeaking + 2); ++band) {
        const BandConfig & bandCfg = presetCfg.bandConfigs[band];
        setGain(band, bandCfg.gain);
        setFrequency(band, bandCfg.freq);
        setBandwidth(band, bandCfg.bandwidth);
    }
    mCurPreset = preset;
}

void AudioEqualizer::commit(bool immediate) {
    ALOGV("AudioEqualizer::commit(immediate=%d)", immediate);
    mpLowShelf->commit(immediate);
    for (int i = 0; i < mNumPeaking; ++i) {
        mpPeakingFilters[i].commit(immediate);
    }
    mpHighShelf->commit(immediate);
}

void AudioEqualizer::process(const audio_sample_t * pIn,
                             audio_sample_t * pOut,
                             int frameCount) {
//    ALOGV("AudioEqualizer::process(frameCount=%d)", frameCount);
    mpLowShelf->process(pIn, pOut, frameCount);
    for (int i = 0; i < mNumPeaking; ++i) {
        mpPeakingFilters[i].process(pIn, pOut, frameCount);
    }
    mpHighShelf->process(pIn, pOut, frameCount);
}

void AudioEqualizer::enable(bool immediate) {
    ALOGV("AudioEqualizer::enable(immediate=%d)", immediate);
    mpLowShelf->enable(immediate);
    for (int i = 0; i < mNumPeaking; ++i) {
        mpPeakingFilters[i].enable(immediate);
    }
    mpHighShelf->enable(immediate);
}

void AudioEqualizer::disable(bool immediate) {
    ALOGV("AudioEqualizer::disable(immediate=%d)", immediate);
    mpLowShelf->disable(immediate);
    for (int i = 0; i < mNumPeaking; ++i) {
        mpPeakingFilters[i].disable(immediate);
    }
    mpHighShelf->disable(immediate);
}

int AudioEqualizer::getMostRelevantBand(uint32_t targetFreq) const {
    // First, find the two bands that the target frequency is between.
    uint32_t low = mpLowShelf->getFrequency();
    if (targetFreq <= low) {
        return 0;
    }
    uint32_t high = mpHighShelf->getFrequency();
    if (targetFreq >= high) {
        return mNumPeaking + 1;
    }
    int band = mNumPeaking;
    for (int i = 0; i < mNumPeaking; ++i) {
        uint32_t freq = mpPeakingFilters[i].getFrequency();
        if (freq >= targetFreq) {
            high = freq;
            band = i;
            break;
        }
        low = freq;
    }
    // Now, low is right below the target and high is right above. See which one
    // is closer on a log scale.
    low = Effects_log2(low);
    high = Effects_log2(high);
    targetFreq = Effects_log2(targetFreq);
    if (high - targetFreq < targetFreq - low) {
        return band + 1;
    } else {
        return band;
    }
}


AudioEqualizer::AudioEqualizer(void * pMem, int nBands, int nChannels,
                               int sampleRate, bool ownMem,
                               const PresetConfig * presets, int nPresets)
                               : mSampleRate(sampleRate)
                               , mpPresets(presets)
                               , mNumPresets(nPresets) {
    assert(pMem != NULL);
    assert(nPresets == 0 || nPresets > 0 && presets != NULL);
    mpMem = ownMem ? pMem : NULL;

    pMem = (char *) pMem + sizeof(AudioEqualizer);
    mpLowShelf = new (pMem) AudioShelvingFilter(AudioShelvingFilter::kLowShelf,
                                                nChannels, sampleRate);
    pMem = (char *) pMem + sizeof(AudioShelvingFilter);
    mpHighShelf = new (pMem) AudioShelvingFilter(AudioShelvingFilter::kHighShelf,
                                                 nChannels, sampleRate);
    pMem = (char *) pMem + sizeof(AudioShelvingFilter);
    mNumPeaking = nBands - 2;
    if (mNumPeaking > 0) {
        mpPeakingFilters = reinterpret_cast<AudioPeakingFilter *>(pMem);
        for (int i = 0; i < mNumPeaking; ++i) {
            new (&mpPeakingFilters[i]) AudioPeakingFilter(nChannels,
                                                          sampleRate);
        }
    }
    reset();
}

}