/*  This file is part of the KDE project.

Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).

This library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2.1 or 3 of the License.

This library 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this library.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "volumeeffect.h"
#include "qbasefilter.h"
#include "qmeminputpin.h"

#include <QtCore/qmath.h> //for sqrt

QT_BEGIN_NAMESPACE

#ifndef QT_NO_PHONON_VOLUMEFADEREFFECT


namespace Phonon
{
    namespace DS9
    {
        /**************************************************************************
        * curve functions
        *************************************************************************/

        static qreal curveValueFadeIn3dB(const qreal fadeStart, const qreal fadeDiff, const qreal completed)
        {
            return (fadeStart + fadeDiff * sqrt(completed));
        }
        static qreal curveValueFadeOut3dB(const qreal fadeStart, const qreal fadeDiff, const qreal completed)
        {
            return (fadeStart + fadeDiff * (1.0 - sqrt(1.0 - completed)));
        }
        // in == out for a linear fade
        static qreal curveValueFade6dB(const qreal fadeStart, const qreal fadeDiff, const qreal completed)
        {
            return (fadeStart + fadeDiff * completed);
        }
        static qreal curveValueFadeIn9dB(const qreal fadeStart, const qreal fadeDiff, const qreal completed)
        {
            return (fadeStart + fadeDiff * pow(completed, 1.5));
        }
        static qreal curveValueFadeOut9dB(const qreal fadeStart, const qreal fadeDiff, const qreal completed)
        {
            return (fadeStart + fadeDiff * (1.0 - pow(1.0 - completed, 1.5)));
        }
        static qreal curveValueFadeIn12dB(const qreal fadeStart, const qreal fadeDiff, const qreal completed)
        {
            return (fadeStart + fadeDiff * completed * completed);
        }
        static qreal curveValueFadeOut12dB(const qreal fadeStart, const qreal fadeDiff, const qreal completed)
        {
            const qreal x = 1.0 - completed;
            return (fadeStart + fadeDiff * (1.0 - x * x));
        }

        static const QVector<AM_MEDIA_TYPE> audioMediaType()
        {
            QVector<AM_MEDIA_TYPE> ret;
            AM_MEDIA_TYPE mt = { MEDIATYPE_Audio, MEDIASUBTYPE_PCM, 1, 0, 1, GUID_NULL, 0, 0, 0};
            ret << mt;
            return ret;
        }

        class VolumeMemInputPin : public QMemInputPin
        {
        public:
            VolumeMemInputPin(QBaseFilter *parent, const QVector<AM_MEDIA_TYPE> &mt, QPin *output) : QMemInputPin(parent, mt, true /*transform*/, output)
            {
            }

            ~VolumeMemInputPin()
            {
            }

            STDMETHODIMP NotifyAllocator(IMemAllocator *alloc, BOOL b)
            {
                ALLOCATOR_PROPERTIES prop;
                HRESULT hr = alloc->GetProperties(&prop);
                if (SUCCEEDED(hr) && prop.cBuffers > 1) {
                    //this allows to reduce the latency for sound
                    //the problem is that too low numbers makes the whole thing fail...
                    ALLOCATOR_PROPERTIES actual;
                    prop.cBuffers = 1;
                    alloc->SetProperties(&prop, &actual);
                }
                return QMemInputPin::NotifyAllocator(alloc, b);
            }

        };

        class VolumeMemOutputPin : public QPin
        {
        public:
            VolumeMemOutputPin(QBaseFilter *parent, const QVector<AM_MEDIA_TYPE> &mt) : QPin(parent, PINDIR_OUTPUT, mt)
            {
            }

            ~VolumeMemOutputPin()
            {
            }

        };

        class VolumeEffectFilter : public QBaseFilter
        {
        public:
            VolumeEffectFilter(VolumeEffect *);

            //reimplementation
            virtual HRESULT processSample(IMediaSample *);

        private:
            void treatOneSamplePerChannel(BYTE **buffer, int sampleSize, int channelCount, int frequency);

            QMemInputPin *m_input;
            QPin *m_output;
            VolumeEffect *m_volumeEffect;
        };

        VolumeEffectFilter::VolumeEffectFilter(VolumeEffect *ve) : QBaseFilter(CLSID_NULL),
            m_volumeEffect(ve)
        {
            QVector<AM_MEDIA_TYPE> mt;

            //creating the output
            m_output = new VolumeMemOutputPin(this, mt);

            //then creating the input
            mt << audioMediaType();
            m_input = new VolumeMemInputPin(this, mt, m_output);
        }

        void VolumeEffectFilter::treatOneSamplePerChannel(BYTE **buffer, int sampleSize, int channelCount, int frequency)
        {
            float volume = m_volumeEffect->volume();
            if (m_volumeEffect->m_fading) {
                const qreal lastSample = m_volumeEffect->m_fadeDuration * frequency / 1000;
                const qreal completed = qreal(m_volumeEffect->m_fadeSamplePosition++) / lastSample;

                if (qFuzzyCompare(completed, qreal(1.))) {
                    m_volumeEffect->setVolume(m_volumeEffect->m_targetVolume);
                    m_volumeEffect->m_fading = false; //we end the fading effect
                } else {
                    volume = m_volumeEffect->m_fadeCurveFn(m_volumeEffect->m_initialVolume, 
                        m_volumeEffect->m_targetVolume - m_volumeEffect->m_initialVolume,
                        completed);
                }
            }

            for(int c = 0; c < channelCount; ++c) {
                switch (sampleSize)
                {
                case 2:
                    {
                        short *shortBuffer = reinterpret_cast<short*>(*buffer);
                        *shortBuffer *= qRound(volume);
                    }
                    break;
                case 1:
                    **buffer *= qRound(volume);
                    break;
                default:
                    break;
                }

                *buffer += sampleSize;
            }
        }

        HRESULT VolumeEffectFilter::processSample(IMediaSample * ms)
        {
            BYTE *buffer = 0;
            ms->GetPointer(&buffer);
            if (buffer) {
                const AM_MEDIA_TYPE &mt = m_output->connectedType();
                if (mt.formattype != FORMAT_WaveFormatEx) {
                    return VFW_E_INVALIDMEDIATYPE;
                }
                WAVEFORMATEX *format = reinterpret_cast<WAVEFORMATEX*>(mt.pbFormat);
                const int channelCount = format->nChannels;
                const int sampleSize = format->wBitsPerSample / 8; //...in bytes


                const BYTE *end = buffer + ms->GetActualDataLength();
                while (buffer < end) {
                    treatOneSamplePerChannel(&buffer, sampleSize, channelCount, format->nSamplesPerSec);
                }
            }

            return S_OK;
        }

        VolumeEffect::VolumeEffect(QObject *parent) : Effect(parent), 
            m_volume(1), m_fadeCurve(Phonon::VolumeFaderEffect::Fade3Decibel),
            m_fading(false), m_initialVolume(0), m_targetVolume(0), m_fadeDuration(0),
            m_fadeSamplePosition(0)
        {
            //creation of the effects
            for(int i = 0; i < FILTER_COUNT; ++i) {
                VolumeEffectFilter *f = new VolumeEffectFilter(this);
                m_filters[i] = Filter(f);
            }
        }

        float VolumeEffect::volume() const
        {
            return m_volume;
        }

        void VolumeEffect::setVolume(float newVolume)
        {
            m_volume = newVolume;
        }

        Phonon::VolumeFaderEffect::FadeCurve VolumeEffect::fadeCurve() const
        {
            return m_fadeCurve;
        }

        void VolumeEffect::setFadeCurve(Phonon::VolumeFaderEffect::FadeCurve curve)
        {
            m_fadeCurve = curve;
        }


        void VolumeEffect::fadeTo(float vol, int duration)
        {
            m_fading = true; //will be set back to false when fading is finished
            m_targetVolume = vol;
            m_fadeSamplePosition = 0;
            m_initialVolume = m_volume;
            m_fadeDuration = duration;

            //in or out?
            const bool in = vol > m_volume;

            switch(m_fadeCurve)
            {
            case Phonon::VolumeFaderEffect::Fade6Decibel:
                m_fadeCurveFn = curveValueFade6dB;
                break;
            case Phonon::VolumeFaderEffect::Fade9Decibel:
                if (in) {
                    m_fadeCurveFn = curveValueFadeIn9dB;
                } else {
                    m_fadeCurveFn = curveValueFadeOut9dB;
                }
                break;
            case Phonon::VolumeFaderEffect::Fade12Decibel:
                if (in) {
                    m_fadeCurveFn = curveValueFadeIn12dB;
                } else {
                    m_fadeCurveFn = curveValueFadeOut12dB;
                }
                break;
            case Phonon::VolumeFaderEffect::Fade3Decibel:
            default:
                if (in) {
                    m_fadeCurveFn = curveValueFadeIn3dB;
                } else {
                    m_fadeCurveFn = curveValueFadeOut3dB;
                }
                break;
            }
        }
    }
}

#endif //QT_NO_PHONON_VOLUMEFADEREFFECT

QT_END_NAMESPACE

#include "moc_volumeeffect.cpp"
