/**************************************************************************
 * Copyright (C) 2008 Cocha                                               *
 * http://sourceforge.jp/projects/ecodecotool/                            *
 *                                                                        *
 *  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, 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 GNU Make; see the file COPYING.  If not, write to          *
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. *
 *                                                                        *
 **************************************************************************/

#include "EcoDecoVorbis.h"
CDecodeVorbis::CDecodeVorbis(IUnknown *pUnk, HRESULT *phr)
   : CSimpleTransform(L"Vorbis Decoder", pUnk, CLSID_DecodeVorbis, phr)
{  

   ::ZeroMemory(&m_outFormat, sizeof(m_outFormat));
   m_bHeaderInitialized = false;
   m_bBlockInitialized = false;
   m_nPacketNumber = 0;
   m_bUseGain = false;
   m_fGain = 1.0f;

   m_outFormat.wFormatTag      = WAVE_FORMAT_IEEE_FLOAT;
   m_outFormat.wBitsPerSample  = 32;

   m_rtCurrent = 0;
}
CDecodeVorbis::~CDecodeVorbis()  
{  
   VorbisClear();
}  
HRESULT CDecodeVorbis::GetOutFormat(void *pFormat)
{  
   CheckPointer(pFormat, E_POINTER);
   ::CopyMemory(pFormat, &m_outFormat, sizeof(m_outFormat));

   return S_OK;
}
HRESULT CDecodeVorbis::SetOutFormat(void *pFormat)
{  

   CheckPointer(pFormat, E_POINTER);
   WAVEFORMATEX *pwf = (WAVEFORMATEX *)pFormat;

   if(pwf->wFormatTag == WAVE_FORMAT_PCM && pwf->wBitsPerSample == 16)
   {
      ::CopyMemory(&m_outFormat, pFormat, sizeof(m_outFormat));
      return S_OK;
   }
   else if(pwf->wFormatTag == WAVE_FORMAT_IEEE_FLOAT && pwf->wBitsPerSample == 32)
   {
      ::CopyMemory(&m_outFormat, pFormat, sizeof(m_outFormat));
      return S_OK;
   }

   return E_FAIL;
}
void CDecodeVorbis::VorbisClear()
{
   if(m_bBlockInitialized == true)
   {
      vorbis_block_clear(&m_vb);
      vorbis_dsp_clear(&m_vd);

      m_bBlockInitialized = false;
   }

   if(m_bHeaderInitialized == true)
   {
      vorbis_comment_clear(&m_vc);
      vorbis_info_clear(&m_vi);

      m_bHeaderInitialized = false;
   }

   m_bUseGain = false;
   m_fGain = 1.0f;
   m_nPacketNumber = 0;
}
HRESULT CDecodeVorbis::OnStart(void)
{  
   m_rtCurrent = 0;
   VorbisClear();
   return S_OK;
}
HRESULT CDecodeVorbis::OnSeek(void)
{  
   m_rtCurrent = 0;
   VorbisClear();
   return S_OK;
}
void CDecodeVorbis::HeaderInitialize(void)
{
   vorbis_info_init(&m_vi);
   vorbis_comment_init(&m_vc);
}
void CDecodeVorbis::BlockInitialize(void)
{
   vorbis_synthesis_init(&m_vd, &m_vi);
   vorbis_block_init(&m_vd, &m_vb);
}
void CDecodeVorbis::ReceiveHeader(BYTE *pPacketData, int nPacketDataLength)
{
   ogg_packet op;

   if(pPacketData[0] == PACKET_HEADER_IDENTIFICATION)
   {  
      op.b_o_s = 1;
      op.e_o_s = 0;
      op.granulepos = 0;
      op.packetno = m_nPacketNumber;
      op.packet = pPacketData;
      op.bytes = nPacketDataLength;
      vorbis_synthesis_headerin(&m_vi, &m_vc, &op);

      m_nPacketNumber++;
   }
   else if(pPacketData[0] == PACKET_HEADER_COMMENT)
   { 
      op.b_o_s = 0;
      op.e_o_s = 0;
      op.granulepos = 0;
      op.packetno = m_nPacketNumber;
      op.packet = pPacketData;
      op.bytes = nPacketDataLength;
      vorbis_synthesis_headerin(&m_vi, &m_vc, &op);

      m_nPacketNumber++;
   }
   else if(pPacketData[0] == PACKET_HEADER_SETUP)
   { 
      op.b_o_s = 0;
      op.e_o_s = 0;
      op.granulepos = 0;
      op.packetno = m_nPacketNumber;
      op.packet = pPacketData;
      op.bytes = nPacketDataLength;
      vorbis_synthesis_headerin(&m_vi, &m_vc, &op);

      m_nPacketNumber++;
   }
}
HRESULT CDecodeVorbis::OnTransform(IMediaSample *pInSample, IMediaSample *pOutSample)
{  

   BYTE *pbOutBuffer = NULL;

   HRESULT hr;
   ogg_packet op;
   int vr, nSampleSize, nActualDataLength;
   REFERENCE_TIME rtStart, rtStop;
   float **ppInBuffer;

   pInSample->GetPointer(&op.packet);
   op.bytes = pInSample->GetActualDataLength();

   pOutSample->GetPointer(&pbOutBuffer);

   if(m_bHeaderInitialized == false)
   {  
      HeaderInitialize();
      m_bHeaderInitialized = true;
   }

   if(op.packet[0] & PACKET_HEADER_BIT)
   {  
      ReceiveHeader(op.packet, op.bytes);

      pOutSample->SetTime(&m_rtCurrent, &m_rtCurrent);
      pOutSample->SetActualDataLength(0);

      return S_OK;
   }

   if(m_bBlockInitialized == false)
   {
      if(vorbis_comment_query_count(&m_vc, "LWING_GAIN"))
      {
         m_bUseGain = true;
         m_fGain = (float)::atof(vorbis_comment_query(&m_vc, "LWING_GAIN", 0));
      }
      else if(vorbis_comment_query_count(&m_vc, "POSTGAIN"))
      {
         m_bUseGain = true;
         m_fGain = (float)::atof(vorbis_comment_query(&m_vc, "POSTGAIN", 0));
      }

      BlockInitialize();
      m_bBlockInitialized = true;
   }

   hr = pInSample->GetTime(&rtStart, &rtStop);
   if FAILED(hr) return hr;

   op.b_o_s = 0;
   op.e_o_s = 0;

   op.packetno = m_nPacketNumber;
   m_nPacketNumber++;

   op.granulepos = (ogg_int64_t)( ((double)rtStart * m_vi.rate / ONE_SECOND) + 0.5);

   vr = vorbis_synthesis(&m_vb, &op);
   if(vr >= 0)
      vorbis_synthesis_blockin(&m_vd, &m_vb);

   if(vorbis_synthesis_pcmout(&m_vd, NULL) == 0)
      return S_OK; 

   nActualDataLength = 0;

   while(true)
   {  
      nSampleSize = vorbis_synthesis_pcmout(&m_vd, &ppInBuffer);
      if(nSampleSize <= 0)
         break;

      if( (nActualDataLength + nSampleSize * m_outFormat.nBlockAlign) < (4096 * m_outFormat.nBlockAlign))
      {
         int i, j;

         if(m_outFormat.wBitsPerSample == 32)
         {
            float *pfOutData = (float *)pbOutBuffer;

            if(m_bUseGain == false)
            {
               for(i=0;i<nSampleSize;i++)
               {
                  for(j=0;j<m_outFormat.nChannels;j++)
                  {
                     *pfOutData = ppInBuffer[j][i];
                     pfOutData++;
                  }
               }
            }
            else
            {
               for(i=0;i<nSampleSize;i++)
               {
                  for(j=0;j<m_outFormat.nChannels;j++)
                  {
                     *pfOutData = (ppInBuffer[j][i] * m_fGain);
                     pfOutData++;
                  }
               }
            }
         }
         else 
         {
            short *psOutData = (short *)pbOutBuffer;
            float fRatioPlus  = 32767.0f;
            float fRatioMinus = 32768.0f;

            if(m_bUseGain == true)
            {
               fRatioPlus *= m_fGain;
               fRatioMinus *= m_fGain;
            }

            for(i=0;i<nSampleSize;i++)
            {
               for(j=0;j<m_outFormat.nChannels;j++)
               {
                  if(ppInBuffer[j][i] > 0.0f)
                  {
                     ppInBuffer[j][i] = ppInBuffer[j][i] * fRatioPlus;

                     if(ppInBuffer[j][i] > 32766.5f)
                        ppInBuffer[j][i] = 32766.5f;

                     *psOutData = short(ppInBuffer[j][i] + 0.5f);
                     psOutData++;
                  }
                  else
                  {
                     ppInBuffer[j][i] = ppInBuffer[j][i] * fRatioMinus;

                     if(ppInBuffer[j][i] < -32767.5f)
                        ppInBuffer[j][i] = -32767.5f;

                     *psOutData = short(ppInBuffer[j][i] - 0.5f);
                     psOutData++;
                  }
               }
            }
         }

         nActualDataLength += (nSampleSize * m_outFormat.nBlockAlign);
      }

      vorbis_synthesis_read(&m_vd, nSampleSize);
   }

   pOutSample->SetSyncPoint(TRUE);
   pOutSample->SetDiscontinuity(FALSE);
   pOutSample->SetPreroll(FALSE);
   pOutSample->SetActualDataLength(nActualDataLength);

   rtStop = m_rtCurrent + (REFERENCE_TIME)((LONGLONG)nActualDataLength * ONE_SECOND / m_outFormat.nAvgBytesPerSec);
   pOutSample->SetTime(&m_rtCurrent, &rtStop);
   m_rtCurrent = rtStop;

   return S_OK;
}
HRESULT CDecodeVorbis::OnConnectInPin(const CMediaType *pmtIn)
{  

   if(pmtIn->majortype != MEDIATYPE_Audio)
      return S_FALSE;

   if(pmtIn->subtype != MEDIASUBTYPE_Vorbis)
      return S_FALSE;

   if(pmtIn->formattype != FORMAT_VorbisFormat)
      return S_FALSE;

   VORBISFORMAT vorbisFormat;
   ::CopyMemory(&vorbisFormat, pmtIn->Format(), sizeof(vorbisFormat));

   m_outFormat.cbSize          = 0;
   m_outFormat.nChannels       = vorbisFormat.nChannels;
   m_outFormat.nSamplesPerSec  = vorbisFormat.nSamplesPerSec;
   m_outFormat.nBlockAlign     = m_outFormat.nChannels * (m_outFormat.wBitsPerSample / 8);
   m_outFormat.nAvgBytesPerSec = m_outFormat.nSamplesPerSec * m_outFormat.nBlockAlign;

   return S_OK;
}
HRESULT CDecodeVorbis::OnConnectOutPin(const CMediaType *pmtIn, int nInBufferSize, CMediaType *pmtOut, int *pnOutBufferSize)
{  

   pmtOut->SetType(&MEDIATYPE_Audio);
   pmtOut->SetSubtype(&MEDIASUBTYPE_PCM);
   pmtOut->SetTemporalCompression(FALSE);
   pmtOut->SetSampleSize(0);
   pmtOut->SetFormatType(&FORMAT_WaveFormatEx);
   pmtOut->SetFormat((BYTE*)&m_outFormat, sizeof(m_outFormat));

   *pnOutBufferSize = 4096 * m_outFormat.nBlockAlign;

   return S_OK;
}
STDMETHODIMP CDecodeVorbis::OnQueryInterface(REFIID riid, void ** ppv)
{
   if(riid == IID_IDecodeInterface)
      return GetInterface((IDecodeInterface *)this, ppv);

   return CTransformFilter::NonDelegatingQueryInterface(riid, ppv);
}
CUnknown * WINAPI CDecodeVorbis::CreateInstance(LPUNKNOWN punk, HRESULT *phr)
{  

   ASSERT(phr);
    
   CDecodeVorbis *pNewObject = new CDecodeVorbis(punk, phr);
   if (pNewObject == NULL)
   {
      if(phr != NULL)
         *phr = E_OUTOFMEMORY;
   }

   return dynamic_cast<CUnknown *>(pNewObject);
}
