// ****************************************************************************
//
//		CMidiInQ.cpp
//
//		Implementation file for the CMidiInQ class.
//		Use a simple fixed size queue for managing MIDI data.
//		Fill & drain pointers are maintained automatically whenever
//		an Add or Get function succeeds.
//
//		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"
 
 
/****************************************************************************
 
	Construction and destruction, clean up and init
 
 ****************************************************************************/
 
//============================================================================
//
// Construction & destructor
//
//============================================================================
 
CMidiInQ::CMidiInQ()
{
 
	m_pBuffer = NULL;
	m_ullLastActivityTime = 0;
	
	m_wMtcState = MIDI_IN_STATE_NORMAL;
 
	m_pEG = NULL;	
	m_pMtcSync = NULL;
	
}	// CMidiInQ::CMidiInQ()
 
 
CMidiInQ::~CMidiInQ()
{
 
	Cleanup();
	
}	// CMidiInQ::~CMidiInQ()
 
 
//============================================================================
//
// Init
//
//============================================================================
 
ECHOSTATUS CMidiInQ::Init( CEchoGals *pEG )
{
	ECHO_DEBUGPRINTF(("CMidiInQ::Init\n"));
 
	m_pEG = pEG;
 
	m_dwFill = 0;
	m_dwBufferSizeMask =  MIDI_IN_Q_SIZE - 1;	// MIDI_IN_Q_SIZE must be a power of 2
									
	return ECHOSTATUS_OK;
									  	
}	// Init
 
 
//============================================================================
//
// Cleanup
//
//============================================================================
 
void CMidiInQ::Cleanup()
{
	//
	// Free the MIDI input buffer
	//
	if (m_pBuffer)
	{
		OsFreeNonPaged( m_pBuffer );
		m_pBuffer = NULL;
	}
	
	//
	// Free the MTC sync object if it exists
	//
	if (m_pMtcSync)
	{
		delete m_pMtcSync;
		m_pMtcSync = NULL;
	}
}	
 
 
 
 
/****************************************************************************
 
	Queue management - add and remove MIDI data
 
 ****************************************************************************/
 
//============================================================================
//
// GetMidi - get oldest MIDI input byte from the Q
//
//============================================================================
 
ECHOSTATUS CMidiInQ::GetMidi
(
	ECHOGALS_MIDI_IN_CONTEXT	*pContext,
	DWORD								&dwMidiByte,
	LONGLONG							&llTimestamp
)
{
	DWORD dwDrain;
 
	if (NULL == m_pBuffer)
		return ECHOSTATUS_CHANNEL_NOT_OPEN;
		
	dwDrain = pContext->dwDrain;
	if ( m_dwFill == dwDrain)
		return ECHOSTATUS_NO_DATA;
	
	dwMidiByte = m_pBuffer[ dwDrain ].dwMidi;
	llTimestamp = m_pBuffer[ dwDrain ].llTimestamp;
	
	//ECHO_DEBUGPRINTF( ("CMidiInQ::GetMidi 0x%lx\n", dwMidiByte) );
 
	dwDrain++;
	dwDrain &= m_dwBufferSizeMask;
	pContext->dwDrain = dwDrain;
	
	return ECHOSTATUS_OK;
	
}	// ECHOSTATUS CMidiInQ::GetMidi
 
 
//============================================================================
//
// AddMidi - add new MIDI input byte to the Q
//
//============================================================================
 
ECHOSTATUS CMidiInQ::AddMidi
(
		DWORD		dwMidiByte,
		LONGLONG	llTimestamp
)
{
	DWORD dwFill;
 
	//ECHO_DEBUGPRINTF( ("CMidiInQ::AddMidi 0x%lx\n", dwMidiByte) );
	
	dwFill = m_dwFill;
	m_pBuffer[ dwFill ].dwMidi = dwMidiByte;
	m_pBuffer[ dwFill ].llTimestamp = llTimestamp;
 
	dwFill++;
	dwFill &= m_dwBufferSizeMask;
	m_dwFill = dwFill;
 
	return ECHOSTATUS_OK;
 
}	// ECHOSTATUS CMidiInQ::AddMidi
 
 
//============================================================================
//
// Reset
//
//============================================================================
 
void CMidiInQ::Reset(ECHOGALS_MIDI_IN_CONTEXT *pContext)
{
	pContext->dwDrain = m_dwFill;
	
}	// void CMidiInQ::Reset()
 
 
 
 
/****************************************************************************
 
	Arm and disarm
 
 ****************************************************************************/
 
 
//============================================================================
//
// Arm - resets the Q and enables the Q to start receiving MIDI input data
//
//============================================================================
 
ECHOSTATUS CMidiInQ::Arm(ECHOGALS_MIDI_IN_CONTEXT *pContext)
{
	ECHOSTATUS Status;
 
	pContext->fOpen = FALSE;
 
	//
	// Return if there is no Echogals pointer
	// 	
	if (NULL == m_pEG)
	{
		return ECHOSTATUS_CANT_OPEN;
	}
		
	//-------------------------------------------------------------------------	
	//
	// Set up the MIDI input buffer
	//
	//-------------------------------------------------------------------------	
	
	if (NULL == m_pBuffer)
	{
		//
		// Reset the buffer pointers
		//
		m_dwFill = 0;
 
		//
		// Allocate
		//
		Status = OsAllocateNonPaged( MIDI_IN_Q_SIZE * sizeof(MIDI_DATA),
												(PPVOID) &m_pBuffer);
		if (Status != ECHOSTATUS_OK)
		{
			ECHO_DEBUGPRINTF(("CMidiInQ::Arm - Could not allocate MIDI input buffer\n"));
			return Status;
		}
	}
 
	Reset(pContext);
	m_dwNumClients++;
 
	//-------------------------------------------------------------------------	
	//
	// Enable MIDI input
	//
	//-------------------------------------------------------------------------	
 
	m_pEG->GetDspCommObject()->SetMidiOn( TRUE );
 
	pContext->fOpen = TRUE;	
	return ECHOSTATUS_OK;
 
} // Arm
 
 
//============================================================================
//
// Disarm - surprisingly, does the opposite of Arm
//
//============================================================================
 
ECHOSTATUS CMidiInQ::Disarm(ECHOGALS_MIDI_IN_CONTEXT *pContext)
{
	//
	// Did this client open the MIDI input?
	//
	if (FALSE == pContext->fOpen)
	{
		ECHO_DEBUGPRINTF(("CMidiInQ::Disarm - trying to disarm with client that isn't open\n"));
		return ECHOSTATUS_CHANNEL_NOT_OPEN;
	}
	
	pContext->fOpen = FALSE;
	
	//
	// Last client?
	//
	if (0 == m_dwNumClients)
		return ECHOSTATUS_OK;
 
	m_dwNumClients--;
 
	if (0 == m_dwNumClients)
	{
		//
		// Tell the DSP to disable MIDI input
		//
		if (m_pEG)
			m_pEG->GetDspCommObject()->SetMidiOn( FALSE );
		
		//
		// Free the MIDI input buffer
		//
		if (m_pBuffer)
		{
			OsFreeNonPaged( m_pBuffer );
			m_pBuffer = NULL;
		}
	}
	
	return ECHOSTATUS_OK;
		
}	// Disarm
 
 
//============================================================================
//
// ArmMtcSync - turns on MIDI time code sync
//
//============================================================================
 
ECHOSTATUS CMidiInQ::ArmMtcSync()
{
	if (NULL == m_pEG)
		return ECHOSTATUS_DSP_DEAD;
		
	if (NULL != m_pMtcSync)
		return ECHOSTATUS_OK;
	
	//
	// Create the MTC sync object
	//	
	m_pMtcSync = new CMtcSync( m_pEG );
	if (NULL == m_pMtcSync)
		return ECHOSTATUS_NO_MEM;
		
	//
	// Tell the DSP to enable MIDI input
	//		
	m_pEG->GetDspCommObject()->SetMidiOn( TRUE );
		
	return ECHOSTATUS_OK;
		
}	// ArmMtcSync
 
 
//============================================================================
//
// DisarmMtcSync - turn off MIDI time code sync
//
//============================================================================
 
ECHOSTATUS CMidiInQ::DisarmMtcSync()
{
	if (NULL == m_pMtcSync)
		return ECHOSTATUS_OK;
	
	if (NULL == m_pEG)
		return ECHOSTATUS_DSP_DEAD;
		
	//
	// Tell the DSP to disable MIDI input
	//
	m_pEG->GetDspCommObject()->SetMidiOn( FALSE );
	
	//
	// Free m_pMtcSync
	//
	CMtcSync *pTemp;	// Use temp variable to avoid killing the object
							// while the ISR is using it	
							
	pTemp = m_pMtcSync;
	m_pMtcSync = NULL;
	delete pTemp;
	
	return ECHOSTATUS_OK;
		
}	// DisarmMtcSync
 
 
 
 
/****************************************************************************
 
	Detect MIDI input activity - see if the driver has recently received
	any MIDI input
 
 ****************************************************************************/
 
BOOL CMidiInQ::IsActive()
{
	ULONGLONG	ullCurTime,ullDelta;
 
	//
	// See if anything has happened recently
	//
	m_pEG->m_pOsSupport->OsGetSystemTime( &ullCurTime );
	ullDelta = ullCurTime - m_ullLastActivityTime;
	
	if (ullDelta > MIDI_ACTIVITY_TIMEOUT_USEC)
		return FALSE;
	
	return TRUE;	
	
}	// IsActive
 
 
 
 
/****************************************************************************
 
	MIDI time code
 
 ****************************************************************************/
 
//===========================================================================
//
// Get and set the base MTC sample rate
//
//===========================================================================
 
 
ECHOSTATUS CMidiInQ::GetMtcBaseRate(DWORD *pdwBaseRate)
{
	ECHOSTATUS Status;
 
	if (m_pMtcSync)
	{
		*pdwBaseRate = m_pMtcSync->m_dwBaseSampleRate;
		Status = ECHOSTATUS_OK;
	}
	else
	{
		*pdwBaseRate = 0;
		Status = ECHOSTATUS_NO_DATA;
	}
	
	return Status;
	
}	// GetMtcBaseRate
 
 
ECHOSTATUS CMidiInQ::SetMtcBaseRate(DWORD dwBaseRate)  
{
	ECHOSTATUS Status;
	
	if (m_pMtcSync)
	{
		m_pMtcSync->m_dwBaseSampleRate = dwBaseRate;
		Status = ECHOSTATUS_OK;
	}
	else
	{
		Status = ECHOSTATUS_NO_DATA;
	}
	
	return Status;
	
}	// SetMtcBaseRate
 
 
//===========================================================================
//
// Run the state machine for MIDI input data
//
// You need this state machine to parse the incoming
// MIDI data stream.  Every time the DSP sees a 0xF1 byte come in,
// it adds the DSP sample position to the MIDI data stream.
// The DSP sample position is represented as a 32 bit unsigned
// value, with the high 16 bits first, followed by the low 16 bits.
//
// The following logic parses the incoming MIDI data.
//
//===========================================================================
 
DWORD CMidiInQ::MtcProcessData( DWORD dwMidiData )
{
	DWORD dwRval;
 
	dwRval = 0;
 
	switch ( m_wMtcState )
	{
 
		case MIDI_IN_STATE_NORMAL :
 
			if ( dwMidiData == 0xF1L )
			{
				m_wMtcState = MIDI_IN_STATE_TS_HIGH;
			}
			break;
 
 
		case MIDI_IN_STATE_TS_HIGH :
		
			if (m_pMtcSync)
				m_pMtcSync->StoreTimestampHigh( dwMidiData );
 
			m_wMtcState = MIDI_IN_STATE_TS_LOW;
			dwRval = MIDI_IN_SKIP_DATA;
			break;
 
 
		case MIDI_IN_STATE_TS_LOW :
 
			if (m_pMtcSync)
				m_pMtcSync->StoreTimestampLow( dwMidiData );
 
			m_wMtcState = MIDI_IN_STATE_F1_DATA;
			dwRval = MIDI_IN_SKIP_DATA;
			break;
 
 
		case MIDI_IN_STATE_F1_DATA :
		
			if (m_pMtcSync)
				m_pMtcSync->StoreMtcData( dwMidiData );
 
			m_wMtcState = MIDI_IN_STATE_NORMAL;
			break;
 
	}
 
	return dwRval;
	
}	// DWORD CMidiInQ::MtcProcessData
 
 
//===========================================================================
// 
// ServiceMtcSync
//
//===========================================================================
 
void CMidiInQ::ServiceMtcSync()
{
	if (m_pMtcSync)
		m_pMtcSync->Sync();
	
}	// ServiceMtcSync
 
 
 
 
/****************************************************************************
 
	Interrupt service
 
 ****************************************************************************/
 
ECHOSTATUS CMidiInQ::ServiceIrq()
{
	DWORD				dwMidiCount;
	WORD				wIndex;
	ECHOSTATUS 		Status;
	CDspCommObject	*pDCO;
	LONGLONG			llTimestamp;
 
	//
	// Store the time for the activity detector
	//	
	m_pEG->m_pOsSupport->OsGetSystemTime( &m_ullLastActivityTime );
 
	//
	// Get the MIDI count
	//
	pDCO = m_pEG->GetDspCommObject();
	pDCO->ReadMidi( 0, dwMidiCount );	  // The count is at index 0
 
#ifdef ECHO_DEBUG
	//ECHO_DEBUGPRINTF( ("\tMIDI interrupt (%ld MIDI bytes)\n", dwMidiCount) );
	if ( dwMidiCount == 0 )
	{
		ECHO_DEBUGBREAK();
	}
#endif
 
	//
	// Get the timestamp
	//
	llTimestamp = m_pEG->m_pOsSupport->GetMidiInTimestamp();
 
	//
	// Get the MIDI data from the comm page
	//
	wIndex = 1;		// First MIDI byte is at index 1
	while ( dwMidiCount-- > 0 )
	{
		DWORD	dwMidiByte;
 
		//
		// Get the MIDI byte
		//
		Status = pDCO->ReadMidi( wIndex++, dwMidiByte );
		if ( ECHOSTATUS_OK != Status )
		{
			ECHO_DEBUGPRINTF(("Failed on ReadMidi\n"));
			ECHO_DEBUGBREAK();	// should never happen...
			break;
		}
 
		//
		// Parse the incoming MIDI stream.  The incoming MIDI data consists
		// of MIDI bytes and timestamps for the MIDI time code 0xF1 bytes.
		// MtcProcessData is a little state machine that parses the stream.
		//
		// If you get MIDI_IN_SKIP_DATA back, then this is a timestamp byte,
		// not a MIDI byte, so don't store it in the MIDI input buffer.
		//
		if ( MIDI_IN_SKIP_DATA == MtcProcessData( dwMidiByte ) )
			continue;
			
		//
		// Only store the MIDI data if there is at least one 
		// client
		//
		if (0 != m_dwNumClients)
		{
			//
			// Stash the MIDI data and check for overflow
			//
			if ( ECHOSTATUS_BUFFER_OVERFLOW == AddMidi( dwMidiByte, llTimestamp ) )
			{
				break;
			}
			
		}
	}		// while there is more MIDI data to read
	
	return ECHOSTATUS_OK;
	
} // ServiceIrq
 
 
// *** CMidiInQ.cpp ***

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: m_dwFill, m_dwBufferSizeMask, m_dwNumClients.