⛏️ index : haiku.git

// ****************************************************************************
//
//		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 ***