/*  Fader.h

    flatzebra - SDL-based sound renderer
    Copyright (C) 2011 Pierre Sarrazin <http://sarrazip.com/>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public
    License along with this program; if not, write to the Free
    Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA  02110-1301, USA.
*/

#pragma once

#include <flatzebra/roundbeetle/SampleSource.h>


namespace roundbeetle {


class Fader : public SampleSource
{
public:

    enum FadeType
    {
        LINEAR,
        INV_EXP_4,
    };

    // _toneFreq; frequency in Hz of the tone to generate.
    // _linAtten: 0..1: attenuation goes from 1 to linAtten, over specified number of seconds.
    // _rendererFreq: frequency in Hz of the renderer buffer (e.g. 22050 Hz).
    //
    // Becomes owner of _src, which must come from new.
    //
    Fader(SampleSource *_src, float _targetLinVol, float _durationInSeconds, FadeType _fadeType)
    :   src(_src),
        sampleCounter(0),
        numFadedSamples(std::max(size_t(_durationInSeconds * SoundRenderer::freq()), size_t(1))),
        targetLinVol(_targetLinVol),
        linReduction(1 - targetLinVol),
        curAtten(1),
        fadeFuncPtr(linearFadeOut)
    {
        assert(_src != NULL);
        assert(_targetLinVol >= 0 && _targetLinVol <= 1);

        switch (_fadeType)
        {
        case LINEAR: fadeFuncPtr = linearFadeOut; break;
        case INV_EXP_4: fadeFuncPtr = invExp4FadeOut; break;
        default: assert(!"invalid fade type");
        }
    }

    // Destroys SampleSource.
    //
    virtual ~Fader()
    {
        delete src;
    }

    // Returns mono PCM-16 signal.
    //
    virtual size_t getSamples(Sint16 *dest, size_t numRequested) override
    {
        size_t numObtained = src->getSamples(dest, numRequested);
        if (numObtained == 0)
            return 0;

        for (size_t i = 0; i < numObtained; ++i, ++sampleCounter)
        {
            if (sampleCounter < numFadedSamples)
            {
                float x = sampleCounter / float(numFadedSamples);  // x is in 0..1
                float y = (*fadeFuncPtr)(x);  // y is in 1..0
                assert(y >= 0 && y <= 1.00001f);
                curAtten = y * linReduction + targetLinVol;
                assert(curAtten >= 0 && curAtten <= 1.00001f);
            }

            //std::cout << i << ". " << sampleCounter << ": " << curAtten << std::endl;

            dest[i] = Sint16(dest[i] * curAtten);
        }

        return numObtained;
    }

    virtual bool isFinished() const override
    {
        return src->isFinished();
    }

    virtual bool rewind() override
    {
        if (! src->rewind())
            return false;
        sampleCounter = 0;
        return true;
    }

private:

    // Fade-out functions. Must take 0..1 and return 0..1.

    static float linearFadeOut(float x)
    {
        return 1 - x;
    }

    // expf(x) ** -4. Abrupt fade-out.
    static float invExp4FadeOut(float x)
    {
        float exp = expf(x);
        float exp2 = exp * exp;
        return 1 / (exp2 * exp2);
    }

    Fader(const Fader &);
    Fader &operator = (const Fader &);

private:

    SampleSource *src;
    size_t sampleCounter;
    const size_t numFadedSamples;
    const float targetLinVol;
    const float linReduction;
    float curAtten;
    float (*fadeFuncPtr)(float);

};


}  // namespace roundbeetle
