root/src/add-ons/kernel/drivers/audio/echo/generic/CMtcSync.cpp
// ****************************************************************************
//
//              CMtcSync.cpp
//
//              Implementation of the CMtcSync class.
//              Used to sync to MIDI time code.
//
//              Set editor tabs to 3 for your viewing pleasure.
//
// ----------------------------------------------------------------------------
//
// This file is part of Echo Digital Audio's generic driver library.
// Copyright Echo Digital Audio Corporation (c) 1998 - 2005
// All rights reserved
// www.echoaudio.com
//
// 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 of the License, or (at your option) any later version.
//
// 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, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// ****************************************************************************

#include        "CEchoGals.h"
#include        "CMtcSync.h"


/****************************************************************************

        MTC frames per second lookup

 ****************************************************************************/
 
static DWORD dwMtcFpsLookup[] = 
{
        24,
        25,
        30,     // 30 frames per second, drop frame
        30
};


/****************************************************************************

        Construction, destruction and reset

 ****************************************************************************/

CMtcSync::CMtcSync( CEchoGals *pEG )
{
        m_pEG = pEG;
        
        pEG->GetAudioSampleRate( &m_dwBaseSampleRate );

        //
        //      Buffer init
        //
        m_dwFill = 0;
        m_dwDrain = 0;
        
        //
        // Reset the sync state info
        //
        Reset();
        
}       // CMtcSync::CMtcSync()


CMtcSync::~CMtcSync()
{
}       // CMtcSync::~CMtcSync()


void CMtcSync::Reset()
{
        ECHO_DEBUGPRINTF(("\t **** CMtcSync::Reset\n"));

        m_dwFramesPerSec = 30;

        m_iSamplesPerDframe = (m_dwBaseSampleRate / m_dwFramesPerSec) * 2;
        
        m_dwLastDframeTimestamp = 0;
        m_dwLastDframe = 0;
        m_dwCurrentDframe = 0;
        m_iNumDframesSynced = 0;
        m_iNumSamplesSynced = 0;
        m_dwNextQfType = 0;
        m_dwTemp = 0;

}       // void CMtcSync::Reset()


/****************************************************************************

        Methods for storing MTC data - called at interrupt time

 ****************************************************************************/

//===========================================================================
//
// Store the upper 16 bits of the sample timestamp
//
//===========================================================================
                                                  
void CMtcSync::StoreTimestampHigh(DWORD dwData)
{

        m_Buffer[ m_dwFill ].dwTimestamp = dwData << 16;
        
}       // StoreTimestampHigh


//===========================================================================
//
// Store the lower 16 bits of the sample timestamp
//
//===========================================================================
                                                  
void CMtcSync::StoreTimestampLow(DWORD dwData)
{

        m_Buffer[ m_dwFill ].dwTimestamp |= dwData;
        
}       // StoreTimestampLow


//===========================================================================
//
// Store the MTC data byte
//
//===========================================================================

void CMtcSync::StoreMtcData(DWORD dwData)
{
        //
        //      Store it
        // 
        m_Buffer[ m_dwFill ].dwData = dwData;
        
        //
        // Increment and wrap the pointer
        //
        m_dwFill++;
        m_dwFill &= ECHO_MTC_QUEUE_SZ - 1;
        
}       // StoreMtcData


/****************************************************************************

  Sync to MIDI time code

 ****************************************************************************/
 
void CMtcSync::Sync()
{
        BOOL fRateAdjusted;
        
        //------------------------------------------------------------------------
        //
        // Process each MTC_DATA in the buffer until the buffer is emtpy
        // or the sample rate has been adjusted
        //
        //------------------------------------------------------------------------
        
        fRateAdjusted = FALSE;
        while ( (FALSE == fRateAdjusted) && (m_dwFill != m_dwDrain) )
        {
                //---------------------------------------------------------------------
                //
                // Parse the quarter-frame message
                //
                //---------------------------------------------------------------------

                DWORD dwData,dwTimestamp,dwQfType,dwQfData;

                dwData = m_Buffer[ m_dwDrain ].dwData;          
                dwTimestamp = m_Buffer[ m_dwDrain ].dwTimestamp;
                
                ECHO_DEBUGPRINTF(("\n\tCMtcSync::Sync - data 0x%lx, timestamp 0x%lx\n",
                                                                dwData,dwTimestamp));
                
                dwQfType = (dwData >> 4) & 0xf;
                dwQfData = dwData & 0xf;
                
                m_dwDrain++;
                m_dwDrain &= ECHO_MTC_QUEUE_SZ - 1;
                
                //
                // Make sure the QFs arrive in sequence
                //
                if (dwQfType != m_dwNextQfType)
                {
                        ECHO_DEBUGPRINTF(("\tCMtcSync::Sync - quarter-frames out of sequence\n"));
                        Reset();
                        continue;                       
                }
                
                //
                // Process this QF - accumulate the current dframe number
                //
                switch (dwQfType)
                {
                        case MTC_QF_FRAME_LSN :
                                m_dwTemp = dwQfData;
                                
                                ECHO_DEBUGPRINTF(("\t\tQF frame LSN %ld  m_dwTemp %ld  m_dwCurrentDframe %ld\n",
                                                                                dwQfData,m_dwTemp,m_dwCurrentDframe));
                                
                                break;
                                
                        case MTC_QF_FRAME_MSN :
                                m_dwTemp |= dwQfData << 4;
                                m_dwTemp &= 0x1f;
                                m_dwCurrentDframe = m_dwTemp;
                                
                                ECHO_DEBUGPRINTF(("\t\tQF frame MSN %ld  m_dwTemp %ld  m_dwCurrentDframe %ld\n",
                                                                                dwQfData,m_dwTemp,m_dwCurrentDframe));
                                break;
                                
                        case MTC_QF_SECOND_LSN :
                                m_dwTemp = dwQfData;
                                
                                ECHO_DEBUGPRINTF(("\t\tQF second LSN %ld  m_dwTemp %ld  m_dwCurrentDframe %ld\n",
                                                                                dwQfData,m_dwTemp,m_dwCurrentDframe));                          
                                break;
                                
                        case MTC_QF_SECOND_MSN :
                                m_dwTemp |= dwQfData << 4;
                                m_dwTemp &= 0x3f;
                                m_dwCurrentDframe += m_dwTemp * m_dwFramesPerSec;

                                ECHO_DEBUGPRINTF(("\t\tQF second MSN %ld  m_dwTemp %ld  m_dwCurrentDframe %ld\n",
                                                                                dwQfData,m_dwTemp,m_dwCurrentDframe));
                                break;
                                
                        case MTC_QF_MINUTE_LSN :
                                m_dwTemp = dwQfData;

                                ECHO_DEBUGPRINTF(("\t\tQF minute LSN %ld  m_dwTemp %ld  m_dwCurrentDframe %ld\n",
                                                                                dwQfData,m_dwTemp,m_dwCurrentDframe));                          
                                break;
                                
                        case MTC_QF_MINUTE_MSN :
                                m_dwTemp |= dwQfData << 4;
                                m_dwTemp &= 0x3f;
                                m_dwCurrentDframe += m_dwTemp * m_dwFramesPerSec * 60;

                                ECHO_DEBUGPRINTF(("\t\tQF minute MSN %ld  m_dwTemp %ld  m_dwCurrentDframe %ld\n",
                                                                                dwQfData,m_dwTemp,m_dwCurrentDframe));                          
                                break;
                                
                        case MTC_QF_HOUR_LSN :
                                m_dwTemp = dwQfData;

                                ECHO_DEBUGPRINTF(("\t\tQF hour LSN %ld  m_dwTemp %ld  m_dwCurrentDframe %ld\n",
                                                                                dwQfData,m_dwTemp,m_dwCurrentDframe));                          
                                break;
                                
                        case MTC_QF_HOUR_MSN :
                                //
                                //      Finish the dframe number
                                // 
                                m_dwTemp |= dwQfData << 4;
                                m_dwTemp &= 0x1f;
                                m_dwCurrentDframe += m_dwTemp * m_dwFramesPerSec * 3600;
                                
                                ECHO_DEBUGPRINTF(("\t\tQF hour MSN %ld  m_dwCurrentDframe %ld  m_dwCurrentDframe %ld\n",
                                                                                dwQfData,m_dwTemp,m_dwCurrentDframe));  
                                                                                
                                //
                                // Store the number of frames per second
                                //
                                DWORD dwFpsIndex,dwFps;
                                
                                dwFpsIndex = (dwQfData >> 1) & 3;
                                dwFps = dwMtcFpsLookup[ dwFpsIndex ];
                                m_dwFramesPerSec = dwFps;                               
                                m_iSamplesPerDframe = (m_dwBaseSampleRate / dwFps) * 2;                         
                                break;
                                
                        default :
                                Reset();
                                break;
                        
                }
                
                //
                // If this is not the end of the dframe, go to the next MTC_DATA
                //
                if (MTC_QF_HOUR_MSN != dwQfType)
                {
                        m_dwNextQfType = dwQfType + 1;
                        continue;
                }

                //---------------------------------------------------------------------
                //
                // End of doubleframe reached; see if the card is synced or not
                //
                //---------------------------------------------------------------------
                
                INT32 iFrameDelta;
                
                ECHO_DEBUGPRINTF(("\n\n\t\t\tCMtcSync::Sync - doubleframe %ld\n",m_dwCurrentDframe));
                        
                        
                //
           // Make sure the doubleframes are arriving in sequence
                //
           // Even though we are checking that doubleframes are in sequence, with drop-frame
           // you can sometimes have doubleframes that differ by three, so make sure the 
           // frame diff is no more than 3
           //
           // Should probably check for drop-frame here and only allow diff of 3 for 
                // drop-frame, but who really cares?
                //
                iFrameDelta = (INT32) (m_dwCurrentDframe - m_dwLastDframe);
                if ((iFrameDelta > 3) || (iFrameDelta < 0))
                {
                        DWORD dwDframe;
                
                        ECHO_DEBUGPRINTF(("\t****** CMtcSync::Sync - double-frames out of sequence\n"));                
                        
                        dwDframe = m_dwCurrentDframe;
                        Reset();                        
                        m_dwLastDframe = dwDframe;
                        continue;       
                }
                
                //
                // See if the timestamps have wrapped around
                //
                if (m_dwLastDframeTimestamp > dwTimestamp)
                {
                        ECHO_DEBUGPRINTF(("\tCMtcSync::Sync - timestamps have wrapped around"));                
                        Reset();
                        continue;
                }
                
                //
                // See if the sample rate needs to be adjusted
                //
                INT32 iTimestampDelta,iActualTotalSamples,iExpectedTotalSamples,iVariance;

                // Calculate the timestamp delta; limit the minimum and maximum         
                iTimestampDelta = (INT32) (dwTimestamp - m_dwLastDframeTimestamp);
                
                if (iTimestampDelta > ((INT32) (m_iSamplesPerDframe * 2)) )
                        iTimestampDelta = m_iSamplesPerDframe * 2;
                else if (iTimestampDelta < ((INT32) (m_iSamplesPerDframe / 2)) )        
                        iTimestampDelta = m_iSamplesPerDframe / 2;

                // How many sample times should have gone by?           
                iExpectedTotalSamples = (m_iNumDframesSynced + 1)       * m_iSamplesPerDframe;
                
                ECHO_DEBUGPRINTF(("\t\t\tdwTimestamp 0x%lx  m_dwLastDframeTimestamp 0x%lx\n",dwTimestamp,m_dwLastDframeTimestamp));
                ECHO_DEBUGPRINTF(("\t\t\tBase rate %ld   m_iSamplesPerDframe %ld  iTimestampDelta %ld\n",
                                                                m_dwBaseSampleRate,m_iSamplesPerDframe,iTimestampDelta));
                
                // iVariance represents the difference between expected and actual elapsed
                // sample time                                                                          
                iActualTotalSamples = iTimestampDelta + m_iNumSamplesSynced;
                iVariance = iActualTotalSamples - iExpectedTotalSamples;
                if (iVariance < 0) iVariance = -iVariance;
                
                ECHO_DEBUGPRINTF(("\t\t\tiExpectedTotalSamples %ld   iVariance %ld\n",
                                                                iExpectedTotalSamples,iVariance));
                
                if (iVariance > MTC_TOLERANCE)
                {
                        DWORD dwSampleRate;
                
                        //------------------------------------------------------------------
                        //
                        // The clock needs to be adjusted by this ratio
                        //
                        //           iExpectedTotalSamples
                        //                      -----------------------------
                        //                           iActualTotalSamples
                        //
                        // This math is done in a fixed point fractional format, with 12 
                        // bits after the decimal point
                        //
                        //------------------------------------------------------------------
                        
                        INT32 iRatio;
                        
                        iRatio = (iExpectedTotalSamples << 12) / iActualTotalSamples;
                        
                        //
                        // To avoid wild swings in the sample rate, apply damping to the ratio; 
                        // that is, only change by a small percentage of the ratio.  This is only
                        // done if the ratio is not already small
                        //
                        if (    (iRatio < DAMPING_RATIO_LIMIT_LOW) || 
                                        (iRatio > DAMPING_RATIO_LIMIT_HIGH))
                        {
                                iRatio = (iRatio * (0x1000 - MTC_DAMPING)) >> 12;
                                iRatio += MTC_DAMPING;
                        }
                        
                        ECHO_DEBUGPRINTF(("\t\t\tiRatio 0x%08lx\n",iRatio));
                        ECHO_DEBUGPRINTF(("\t\t\tiExpectedTotalSamples %ld  iActualTotalSamples %ld\n",
                                                                        iExpectedTotalSamples,iActualTotalSamples));
                        ECHO_DEBUGPRINTF(("\t\t\tiTimestampDelta %ld   m_iSamplesPerDframe %ld\n",
                                                                        iTimestampDelta,m_iSamplesPerDframe));          
                                                        

                        //
                        // Adjust the sample rate
                        //
                        dwSampleRate = m_pEG->GetDspCommObject()->GetSampleRate();
                        dwSampleRate = (iRatio * dwSampleRate ) >> 12;
                        
                        // Limit for single or double speed mode
                        if (m_dwBaseSampleRate > 50000)
                        {
                                if (dwSampleRate > 100000)
                                        dwSampleRate = 100000;
                                else if (dwSampleRate < 50000)
                                        dwSampleRate = 50000;   
                        }
                        else
                        {
                                if (dwSampleRate > 50000)
                                        dwSampleRate = 50000;
                                else if (dwSampleRate < MIN_MTC_1X_RATE)
                                        dwSampleRate = MIN_MTC_1X_RATE; 
                        }
                        
                        ECHO_DEBUGPRINTF(("\t\t\tNew sample rate %ld\n",dwSampleRate));
        
                        m_pEG->GetDspCommObject()->SetSampleRate ( dwSampleRate );      
                        
                        //
                        // Reset the sync counts
                        //                      
                        m_iNumDframesSynced = 0;
                        m_iNumSamplesSynced = 0;
                }
                else
                {
                        //------------------------------------------------------------------
                        //
                        // Sample rate is close enough
                        //
                        //------------------------------------------------------------------
                        
                        ECHO_DEBUGPRINTF(("\t\t\tClose enough - iVariance %ld\n",iVariance));
                        
                        m_iNumDframesSynced++;
                        m_iNumSamplesSynced += iTimestampDelta;
                        
                        if (m_iNumDframesSynced > MAX_DFRAME_SYNC_COUNT)
                                m_iNumDframesSynced >>= 1;
                        
                        ECHO_DEBUGPRINTF(("\t\t\tm_iNumDframesSynced %ld  m_iNumSamplesSynced %ld\n\n",
                                                                        m_iNumDframesSynced,m_iNumSamplesSynced));
                }

                m_dwLastDframe = m_dwCurrentDframe;
                m_dwLastDframeTimestamp = dwTimestamp;
                
                m_dwNextQfType = 0;
                
        }                               
        
        
}       // Sync




/****************************************************************************

        New & delete

 ****************************************************************************/

PVOID CMtcSync::operator new( size_t Size )
{
        PVOID           pMemory;
        ECHOSTATUS      Status;
        
        Status = OsAllocateNonPaged(Size,&pMemory);
        
        if ( (ECHOSTATUS_OK != Status) || (NULL == pMemory ))
        {
                ECHO_DEBUGPRINTF(("CMtcSync::operator new - memory allocation failed\n"));

                pMemory = NULL;
        }
        else
        {
                memset( pMemory, 0, Size );
        }

        return pMemory;

}       // PVOID CMtcSync::operator new( size_t Size )


VOID  CMtcSync::operator delete( PVOID pVoid )
{
        if ( ECHOSTATUS_OK != OsFreeNonPaged( pVoid ) )
        {
                ECHO_DEBUGPRINTF( ("CMtcSync::operator delete memory free failed\n") );
        }
}       // VOID  CMtcSync::operator delete( PVOID pVoid )


// *** CMtcSync.cpp ***