/*
 * Copyright (C) 2010 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.
 */

/* Equalizer implementation */

#include "sles_allinclusive.h"

// FIXME move to platform-specific configuration

#define MAX_EQ_PRESETS 3

static const struct EqualizerBand EqualizerBands[MAX_EQ_BANDS] = {
    {1000, 1500, 2000},
    {2000, 3000, 4000},
    {4000, 5500, 7000},
    {7000, 8000, 9000}
};

static const struct EqualizerPreset {
    const char *mName;
    SLmillibel mLevels[MAX_EQ_BANDS];
} EqualizerPresets[MAX_EQ_PRESETS] = {
    {"Default", {0, 0, 0, 0}},
    {"Bass", {500, 200, 100, 0}},
    {"Treble", {0, 100, 200, 500}}
};

static SLresult IEqualizer_SetEnabled(SLEqualizerItf self, SLboolean enabled)
{
    IEqualizer *this = (IEqualizer *) self;
    interface_lock_poke(this);
    this->mEnabled = SL_BOOLEAN_FALSE != enabled; // normalize
    interface_unlock_poke(this);
    return SL_RESULT_SUCCESS;
}

static SLresult IEqualizer_IsEnabled(SLEqualizerItf self, SLboolean *pEnabled)
{
    if (NULL == pEnabled)
        return SL_RESULT_PARAMETER_INVALID;
    IEqualizer *this = (IEqualizer *) self;
    interface_lock_peek(this);
    SLboolean enabled = this->mEnabled;
    interface_unlock_peek(this);
    *pEnabled = enabled;
    return SL_RESULT_SUCCESS;
}

static SLresult IEqualizer_GetNumberOfBands(SLEqualizerItf self, SLuint16 *pNumBands)
{
    if (NULL == pNumBands)
        return SL_RESULT_PARAMETER_INVALID;
    IEqualizer *this = (IEqualizer *) self;
    // Note: no lock, but OK because it is const
    *pNumBands = this->mNumBands;
    return SL_RESULT_SUCCESS;
}

static SLresult IEqualizer_GetBandLevelRange(SLEqualizerItf self, SLmillibel *pMin, SLmillibel *pMax)
{
    if (NULL == pMin && NULL == pMax)
        return SL_RESULT_PARAMETER_INVALID;
    IEqualizer *this = (IEqualizer *) self;
    // Note: no lock, but OK because it is const
    if (NULL != pMin)
        *pMin = this->mBandLevelRangeMin;
    if (NULL != pMax)
        *pMax = this->mBandLevelRangeMax;
    return SL_RESULT_SUCCESS;
}

static SLresult IEqualizer_SetBandLevel(SLEqualizerItf self, SLuint16 band, SLmillibel level)
{
    IEqualizer *this = (IEqualizer *) self;
    if (!(this->mBandLevelRangeMin <= level && level <= this->mBandLevelRangeMax))
        return SL_RESULT_PARAMETER_INVALID;
    if (band >= this->mNumBands)
        return SL_RESULT_PARAMETER_INVALID;
    interface_lock_exclusive(this);
    this->mLevels[band] = level;
    this->mPreset = SL_EQUALIZER_UNDEFINED;
    interface_unlock_exclusive(this);
    return SL_RESULT_SUCCESS;
}

static SLresult IEqualizer_GetBandLevel(SLEqualizerItf self, SLuint16 band, SLmillibel *pLevel)
{
    if (NULL == pLevel)
        return SL_RESULT_PARAMETER_INVALID;
    IEqualizer *this = (IEqualizer *) self;
    if (band >= this->mNumBands)
        return SL_RESULT_PARAMETER_INVALID;
    interface_lock_peek(this);
    SLmillibel level = this->mLevels[band];
    interface_unlock_peek(this);
    *pLevel = level;
    return SL_RESULT_SUCCESS;
}

static SLresult IEqualizer_GetCenterFreq(SLEqualizerItf self, SLuint16 band, SLmilliHertz *pCenter)
{
    if (NULL == pCenter)
        return SL_RESULT_PARAMETER_INVALID;
    IEqualizer *this = (IEqualizer *) self;
    if (band >= this->mNumBands)
        return SL_RESULT_PARAMETER_INVALID;
    // Note: no lock, but OK because it is const
    *pCenter = this->mBands[band].mCenter;
    return SL_RESULT_SUCCESS;
}

static SLresult IEqualizer_GetBandFreqRange(SLEqualizerItf self, SLuint16 band,
    SLmilliHertz *pMin, SLmilliHertz *pMax)
{
    if (NULL == pMin && NULL == pMax)
        return SL_RESULT_PARAMETER_INVALID;
    IEqualizer *this = (IEqualizer *) self;
    if (band >= this->mNumBands)
        return SL_RESULT_PARAMETER_INVALID;
    // Note: no lock, but OK because it is const
    if (NULL != pMin)
        *pMin = this->mBands[band].mMin;
    if (NULL != pMax)
        *pMax = this->mBands[band].mMax;
    return SL_RESULT_SUCCESS;
}

static SLresult IEqualizer_GetBand(SLEqualizerItf self, SLmilliHertz frequency, SLuint16 *pBand)
{
    if (NULL == pBand)
        return SL_RESULT_PARAMETER_INVALID;
    IEqualizer *this = (IEqualizer *) self;
    // search for band whose center frequency has the closest ratio to 1.0
    // assumes bands are unsorted (a pessimistic assumption)
    // assumes bands can overlap (a pessimistic assumption)
    // assumes a small number of bands, so no need for a fancier algorithm
    const struct EqualizerBand *band;
    float floatFreq = (float) frequency;
    float bestRatio = 0.0;
    SLuint16 bestBand = SL_EQUALIZER_UNDEFINED;
    for (band = this->mBands; band < &this->mBands[this->mNumBands]; ++band) {
        if (!(band->mMin <= frequency && frequency <= band->mMax))
            continue;
        assert(band->mMin <= band->mCenter && band->mCenter <= band->mMax);
        assert(band->mCenter != 0);
        float ratio = frequency <= band->mCenter ?
            floatFreq / band->mCenter : band->mCenter / floatFreq;
        if (ratio > bestRatio) {
            bestRatio = ratio;
            bestBand = band - this->mBands;
        }
    }
    *pBand = band - this->mBands;
    return SL_RESULT_SUCCESS;
}

static SLresult IEqualizer_GetCurrentPreset(SLEqualizerItf self, SLuint16 *pPreset)
{
    if (NULL == pPreset)
        return SL_RESULT_PARAMETER_INVALID;
    IEqualizer *this = (IEqualizer *) self;
    interface_lock_shared(this);
    SLuint16 preset = this->mPreset;
    interface_unlock_shared(this);
    *pPreset = preset;
    return SL_RESULT_SUCCESS;
}

static SLresult IEqualizer_UsePreset(SLEqualizerItf self, SLuint16 index)
{
    IEqualizer *this = (IEqualizer *) self;
    if (index >= this->mNumPresets)
        return SL_RESULT_PARAMETER_INVALID;
    interface_lock_exclusive(this);
    SLuint16 band;
    for (band = 0; band < this->mNumBands; ++band)
        this->mLevels[band] = EqualizerPresets[index].mLevels[band];
    this->mPreset = index;
    interface_unlock_exclusive(this);
    return SL_RESULT_SUCCESS;
}

static SLresult IEqualizer_GetNumberOfPresets(SLEqualizerItf self, SLuint16 *pNumPresets)
{
    if (NULL == pNumPresets)
        return SL_RESULT_PARAMETER_INVALID;
    IEqualizer *this = (IEqualizer *) self;
    // Note: no lock, but OK because it is const
    *pNumPresets = this->mNumPresets;
    return SL_RESULT_SUCCESS;
}

static SLresult IEqualizer_GetPresetName(SLEqualizerItf self, SLuint16 index, const SLchar ** ppName)
{
    if (NULL == ppName)
        return SL_RESULT_PARAMETER_INVALID;
    IEqualizer *this = (IEqualizer *) self;
    if (index >= this->mNumPresets)
        return SL_RESULT_PARAMETER_INVALID;
    *ppName = (SLchar *) this->mPresets[index].mName;
    return SL_RESULT_SUCCESS;
}

static const struct SLEqualizerItf_ IEqualizer_Itf = {
    IEqualizer_SetEnabled,
    IEqualizer_IsEnabled,
    IEqualizer_GetNumberOfBands,
    IEqualizer_GetBandLevelRange,
    IEqualizer_SetBandLevel,
    IEqualizer_GetBandLevel,
    IEqualizer_GetCenterFreq,
    IEqualizer_GetBandFreqRange,
    IEqualizer_GetBand,
    IEqualizer_GetCurrentPreset,
    IEqualizer_UsePreset,
    IEqualizer_GetNumberOfPresets,
    IEqualizer_GetPresetName
};

void IEqualizer_init(void *self)
{
    IEqualizer *this = (IEqualizer *) self;
    this->mItf = &IEqualizer_Itf;
    this->mEnabled = SL_BOOLEAN_FALSE;
    this->mPreset = SL_EQUALIZER_UNDEFINED;
    unsigned band;
    for (band = 0; band < MAX_EQ_BANDS; ++band)
        this->mLevels[band] = 0;
    // const fields
    this->mNumPresets = MAX_EQ_PRESETS;
    this->mNumBands = MAX_EQ_BANDS;
    this->mBands = EqualizerBands;
    this->mPresets = EqualizerPresets;
    this->mBandLevelRangeMin = 0;
    this->mBandLevelRangeMax = 1000;
}
