/******************************************************************************
* Backend.cpp *
*-------------*
*  
*------------------------------------------------------------------------------
*  Copyright (C) 1998  Entropic, Inc
*  Copyright (C) 2000 Microsoft Corporation         Date: 03/02/00
*  All Rights Reserved
*
********************************************************************* PACOG ***/

#include "backend.h"
#include "tips.h"
#include "SynthUnit.h"
#include "SpeakerData.h"
#include "vapiIo.h"
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

static const int DEFAULT_F0_SAMPFREQ = 200;
static const double SYNTH_PROPORTION = .1;

class CBEndImp : public CBackEnd 
{
    public:
        CBEndImp();

        ~CBEndImp();

        int  LoadTable (const char* pszFilePath, int iDebug = 0);
        int  GetSampFreq ();
        void SetGain (double dGain);
        void GetSpeakerInfo (int* piBaseLine, int* piRefLine, int* piTopLine);        
        void SetF0SampFreq (int iF0SampFreq = DEFAULT_F0_SAMPFREQ);
        int  NewPhoneString (Phone* phList, int nPh, float* newF0, int nNewF0);
        int  OutputPending ();
        int  GenerateOutput (short** ppnSamples, int* piNumSamples);
        int  GetChunk (ChkDescript** ppChunk);
        bool GetPhoneSetFlag ();
        void SetFrontEndFlag () { m_pSlm->SetFrontEndFlag(); }

    private:
        static const int m_iDefSampFreq;
        CSlm*  m_pSlm;
        CTips* m_pTips;
        int    m_iNumUnits;
        int    m_iCurrUnit;
        int    m_iF0SampFreq;
        float* m_pflF0;
        short* m_pnSynthBuff;
        int    m_iSynthBuffSamples;
        short* m_pnOverflow;
        int    m_iOverflowCurSamples;
        int    m_iOverflowMaxSamples;
        int    m_iPrevSamples;
};

const int CBEndImp::m_iDefSampFreq = 8000;

/*****************************************************************************
* CBackEnd::ClassFactory *
*------------------------*
*   Description:
*
******************************************************************* PACOG ***/
CBackEnd* CBackEnd::ClassFactory()
{
    return new CBEndImp; 
}

/*****************************************************************************
* CBEndImp::CBEndImp *
*--------------------*
*   Description:
*
******************************************************************* PACOG ***/

CBEndImp::CBEndImp()
{
    int iSlmOptions = CSlm::UseGain | CSlm::Blend | CSlm::DynSearch | CSlm::UseTargetF0;
    int iTipsOptions = 0;

    if ( (m_pSlm = CSlm::ClassFactory(iSlmOptions)) == NULL) 
    {
        goto error;
    }
    
    if ((m_pTips = new CTips(iTipsOptions)) == NULL) 
    {
        goto error;
    }

    m_iPrevSamples = 0;
    m_iSynthBuffSamples = 0;
    m_pnSynthBuff = NULL;
    m_pnOverflow = NULL;
    m_iOverflowCurSamples = 0;
    m_iOverflowMaxSamples = 0;

    m_pTips->Init(VAPI_PCM16, m_iDefSampFreq);

    m_iNumUnits  = 0;
    m_iCurrUnit  = 0;
    m_iF0SampFreq  = DEFAULT_F0_SAMPFREQ;
    m_pflF0 = NULL;
  
    return;

error:
    if ( m_pTips )
    {
        delete m_pTips;
        m_pTips = NULL;
    }

    return;
}

/*****************************************************************************
* CBEndImp::~CBEndImp *
*---------------------*
*   Description:
*
******************************************************************* PACOG ***/

CBEndImp::~CBEndImp ()
{
    if (m_pSlm) 
    {
        delete m_pSlm;
    }

    if (m_pTips) 
    {
        delete m_pTips;
    }

    if ( m_pflF0 )
    {
        delete[] m_pflF0;
        m_pflF0 = NULL;
    }

    if ( m_pnSynthBuff )
    {
        delete [] m_pnSynthBuff;
        m_pnSynthBuff = NULL;
    }

    if ( m_pnOverflow )
    {
        delete [] m_pnOverflow;
        m_pnOverflow = NULL;
    }
}


/*****************************************************************************
* CBEndImp::LoadTable *
*---------------------*
*   Description:
*
******************************************************************* PACOG ***/

int CBEndImp::LoadTable (const char* pszFilePath, int iDebug)
{
    assert (pszFilePath);

    if (!m_pSlm->Load (pszFilePath, true)) 
    {
        if (iDebug) 
        {
            fprintf (stderr, "BackEnd: can't load table file %s\n", pszFilePath);
        }
        return 0;
    }

    m_pTips->Init(m_pSlm->GetSampFormat(), m_pSlm->GetSampFreq());

    m_iSynthBuffSamples = SYNTH_PROPORTION * m_pSlm->GetSampFreq();
    if ((m_pnSynthBuff = new short[m_iSynthBuffSamples]) == NULL)
    {
        return 0;
    }

    return 1; 
}


/*****************************************************************************
* CBEndImp::SetGain *
*-------------------*
*   Description:
*
******************************************************************* PACOG ***/
void CBEndImp::SetGain (double dGain)
{
    m_pTips->SetGain(dGain);
}

/*****************************************************************************
* CBEndImp::GetSampFreq *
*-----------------------*
*   Description:
*
******************************************************************* PACOG ***/
int CBEndImp::GetSampFreq ()
{
    return m_pSlm->GetSampFreq();
}

/*****************************************************************************
* CBEndImp::GetSpeakerInfo *
*--------------------------*
*   Description:
*
******************************************************************* PACOG ***/

void CBEndImp::GetSpeakerInfo (int* piBaseLine, int* piRefLine, int* piTopLine)
{
    m_pSlm->GetTtpParam (piBaseLine, piRefLine, piTopLine);
}

/*****************************************************************************
* CBEndImp::GetPhoneSetFlag *
*--------------------------*
*   Description:
*
******************************************************************* mplumpe ***/

bool CBEndImp::GetPhoneSetFlag ()
{
    return m_pSlm->GetPhoneSetFlag ();
}

/*****************************************************************************
* CBEndImp::SetF0SampFreq *
*-------------------------*
*   Description:
*
******************************************************************* PACOG ***/
void CBEndImp::SetF0SampFreq(int iF0SampFreq)
{
    assert (iF0SampFreq>0);
    m_iF0SampFreq = iF0SampFreq;
}

/*****************************************************************************
* CBEndImp::NewPhoneString *
*--------------------------*
*   Description:
*
******************************************************************* PACOG ***/

int CBEndImp::NewPhoneString (Phone* phList, int nPh, float* newF0, int nNewF0)
{
    assert (nPh==0 || phList!=NULL);
    assert (nNewF0==0 || newF0!=NULL);
    int    nF0 = 0;

    m_iCurrUnit = 0;

    if (( m_iNumUnits = m_pSlm->Process (phList, nPh, 0.0)) == 0)
    {
        return 0;
    }

    if ( m_pSlm->GetSynthMethod() )
    {
        m_pSlm->GetNewF0 ( &m_pflF0, &nF0, m_iF0SampFreq);
        m_pTips->NewSentence (m_pflF0, nF0, m_iF0SampFreq);
    }
    else
    {
        m_pTips->NewSentence (newF0, nNewF0, m_iF0SampFreq);
    }

    return 1;
}

/*****************************************************************************
* CBEndImp::OutputPending *
*-------------------------*
*   Description:
*
******************************************************************* PACOG ***/

int CBEndImp::OutputPending ()
{
    return (m_iNumUnits - m_iCurrUnit) || m_pTips->Pending() || m_iOverflowCurSamples;
}

/*****************************************************************************
* CBEndImp::GenerateOutput *
*--------------------------*
*   Description:
*
******************************************************************* PACOG ***/

int CBEndImp::GenerateOutput (short** ppnSamples, int* piNumSamples)
{
    bool fDone = false;
    short* pnCurSamples = NULL;
    int iNumCurSamples = 0;
    
    assert (ppnSamples && piNumSamples && m_pnSynthBuff);
    
    if ( m_iOverflowCurSamples )
    {
        assert( m_pnOverflow );
        memcpy(m_pnSynthBuff, m_pnOverflow, m_iOverflowCurSamples * sizeof(short));
        m_iPrevSamples = m_iOverflowCurSamples;
        m_iOverflowCurSamples = 0;
    }
    
    do 
    {
        while ( m_pTips->Pending() )
        {
            if (m_pTips->NextPeriod( &pnCurSamples, &iNumCurSamples))
            {
                if ( m_iPrevSamples + iNumCurSamples <= m_iSynthBuffSamples )
                {
                    memcpy(&m_pnSynthBuff[m_iPrevSamples], pnCurSamples, iNumCurSamples * sizeof(short));
                    m_iPrevSamples += iNumCurSamples;
                }
                else
                {
                    // something's wrong if a single period exceeds the buffer.
                    assert (iNumCurSamples <= m_iSynthBuffSamples);
                    // is the overflow buffer too small?
                    if ( m_iOverflowMaxSamples < iNumCurSamples )
                    {
                        if ( m_pnOverflow )
                        {
                            delete [] m_pnOverflow;
                            m_pnOverflow = NULL;
                        }
                        if ( (m_pnOverflow = new short[iNumCurSamples]) == NULL )
                        {
                            return 0;
                        }
                        m_iOverflowMaxSamples = iNumCurSamples;
                    }
                    // save the extra in the overflow buffer
                    memcpy(m_pnOverflow, pnCurSamples, iNumCurSamples * sizeof(short));
                    m_iOverflowCurSamples = iNumCurSamples;
                    fDone = true;
                    break;
                }
            }
        }

        if (!fDone)
        {
            if (m_iCurrUnit < m_iNumUnits) 
            {            
                if (! m_pTips->NewUnit (m_pSlm->GetUnit (m_iCurrUnit)))
                {
                    return 0;
                } 
                m_iCurrUnit ++;
            }
            else 
            {
                *ppnSamples  = 0;
                *piNumSamples = 0;
                fDone = true;
            }
            
        }
    } while (! fDone);

    if ( m_iPrevSamples )
    {
        *ppnSamples = m_pnSynthBuff;
        *piNumSamples = m_iPrevSamples;
        m_iPrevSamples = 0;
    }
    else
    {
        *ppnSamples = 0;
        *piNumSamples = 0;
    }
                           
    return 1;
}

/*****************************************************************************
* CBEndImp::GetChunk *
*--------------------*
*   Description:
*
******************************************************************* PACOG ***/

int CBEndImp::GetChunk(ChkDescript** ppChunk)
{
    assert (ppChunk);

    if (!ppChunk)
    {
        return 0;
    }

    if (m_iNumUnits > 0) 
    {        
        if (m_iCurrUnit < m_iNumUnits) 
        {            
            *ppChunk = m_pSlm->GetChunk (m_iCurrUnit++);
        }
    }

    return 1;
}