// ****************************************************************************
//
//		CEchoGals_transport.cpp
//
//		Audio transport methods for the CEchoGals driver class.
//		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"
 
#pragma optimize("",off)
 
 
/******************************************************************************
 
 Functions for opening and closing pipes
 
 ******************************************************************************/
 
//===========================================================================
//
// OpenAudio is used to reserve audio pipes for your exclusive use.  The call
// will fail if someone else has already opened the pipes.  Calling OpenAudio
// is the first step if you want to play or record.
//
// If the fCheckHardware flag is true, then the open will fail
// if the DSP and ASIC firmware have not been loaded (usually means
// your external box is turned off).
//
//===========================================================================
 
ECHOSTATUS CEchoGals::OpenAudio
(
	PECHOGALS_OPENAUDIOPARAMETERS	pOpenParameters,	// Info on pipe
	PWORD									pwPipeIndex,		// Pipe index ptr
	BOOL									fCheckHardware,
	CDaffyDuck							*pDuck
)
{
	CChannelMask	cmMask;
	WORD				wPipeMax, wPipe, wPipeIndex, i, wWidth;
	ECHOSTATUS 		Status;
 
	ECHO_DEBUGPRINTF( ("CEchoGals::OpenAudio: %s %u "
							 "PipeWidth %d "
							 "Cyclic %u \n",
							 ( pOpenParameters->Pipe.bIsInput ) ? "Input" : "Output",
								pOpenParameters->Pipe.nPipe,
								pOpenParameters->Pipe.wInterleave,
								pOpenParameters->bIsCyclic) );
 
	*pwPipeIndex = (WORD) -1;		// So it's never undefined
 
	//
	// Make sure the hardware is OK
	//
	if ( NULL == GetDspCommObject() || GetDspCommObject()->IsBoardBad() )
	{
		ECHO_DEBUGPRINTF( ("\tECHOSTATUS_DSP_DEAD\n") );
		return ECHOSTATUS_DSP_DEAD;
	}
 
	//
	// Make sure the DSP & ASIC are up and running
	//	 - only if fCheckHardware is true
	//
	if (fCheckHardware)
	{
		Status = GetDspCommObject()->LoadFirmware();
 
		if ( ECHOSTATUS_OK != Status )
			return Status;
	}
 
	//
	// Validate the pipe number
	//
	wPipe = pOpenParameters->Pipe.nPipe;
	wWidth = pOpenParameters->Pipe.wInterleave;
	
	if ( pOpenParameters->Pipe.bIsInput )
	{
		wPipeIndex = wPipe + GetNumPipesOut();
		wPipeMax = GetNumPipesIn();
	}
	else
	{
		wPipeIndex = wPipe;
		wPipeMax = GetNumPipesOut();
	}
	
	if ( ( wPipe + wWidth ) > wPipeMax )
	{
		ECHO_DEBUGPRINTF( ("\tECHOSTATUS_INVALID_CHANNEL\n") );
		return ECHOSTATUS_INVALID_CHANNEL;
	}
	
	//
	// If the width is more than two, make sure that this card
	// can handle super interleave
	//
	if (	(0 == (m_wFlags & ECHOGALS_ROFLAG_SUPER_INTERLEAVE_OK)) &&
			(wWidth > 2)
		)
	{
		ECHO_DEBUGPRINTF( ("\tECHOSTATUS_NO_SUPER_INTERLEAVE\n") );
		return ECHOSTATUS_NO_SUPER_INTERLEAVE;
	}
	
	//
	// See if the specified pipes are already open
	//
	for ( i = 0; i < pOpenParameters->Pipe.wInterleave; i++ )
	{
		cmMask.SetIndexInMask( wPipeIndex + i );
	}
	
	if ( m_cmAudioOpen.Test( &cmMask ) )
	{
		ECHO_DEBUGPRINTF( ("OpenAudio - ECHOSTATUS_CHANNEL_ALREADY_OPEN - m_cmAudioOpen 0x%x   cmMask 0x%x\n",
							m_cmAudioOpen.GetMask(),cmMask.GetMask()) );
		return ECHOSTATUS_CHANNEL_ALREADY_OPEN;
	}
 
#ifdef AUTO_DUCK_ALLOCATE
	//
	// Make a daffy duck
	//
	if (NULL == pDuck)
	{
		pDuck = CDaffyDuck::MakeDaffyDuck(m_pOsSupport);
 
		if (NULL == pDuck)
			return ECHOSTATUS_NO_MEM;
	}
 
	SetDaffyDuck( wPipeIndex, pDuck );	
	
#else
 
	//
	// Use the specified duck if one was passed in
	//
	if (NULL != pDuck)
		SetDaffyDuck( wPipeIndex, pDuck );	
		
#endif
		
	
	//
	// Reset the 64-bit DMA position
	//	
	ResetDmaPos(wPipeIndex);
	GetDspCommObject()->ResetPipePosition(wPipeIndex);
 
	//
	// Prep stuff
	//
	m_cmAudioOpen += cmMask;
	if ( pOpenParameters->bIsCyclic )
		m_cmAudioCyclic += cmMask;
	m_Pipes[ wPipeIndex ] = pOpenParameters->Pipe;	
	*pwPipeIndex = wPipeIndex;
	m_ProcessId[ wPipeIndex ] = pOpenParameters->ProcessId;
	Reset( wPipeIndex );
	
	ECHO_DEBUGPRINTF( ("OpenAudio - ECHOSTATUS_OK - m_cmAudioOpen 0x%x\n",m_cmAudioOpen.GetMask()) );
	return ECHOSTATUS_OK;
 
}	// ECHOSTATUS CEchoGals::OpenAudio
 
 
//===========================================================================
//
// CloseAudio is, naturally, the inverse of OpenAudio.
//
//===========================================================================
 
ECHOSTATUS CEchoGals::CloseAudio
(
	PECHOGALS_CLOSEAUDIOPARAMETERS	pCloseParameters,
	BOOL										fFreeDuck
)
{
	CChannelMask	cmMask;
	ECHOSTATUS		Status;
	WORD				i;
	WORD				wPipeIndex;
	
	wPipeIndex = pCloseParameters->wPipeIndex;
 
	ECHO_DEBUGPRINTF( ("CEchoGals::CloseAudio: Pipe %u  ",
							 wPipeIndex) );
 
	Status = VerifyAudioOpen( wPipeIndex );
	if ( ECHOSTATUS_OK != Status )
		return Status;
 
	for ( i = 0;
			i < m_Pipes[ wPipeIndex ].wInterleave;
			i++ )
	{
		cmMask.SetIndexInMask( wPipeIndex + i );
	}
	
	Reset( wPipeIndex );
	
	//
	// Free the scatter-gather list
	//
	if (NULL != m_DaffyDucks[wPipeIndex])
	{
		if (fFreeDuck)
			delete m_DaffyDucks[wPipeIndex];
	
		m_DaffyDucks[wPipeIndex] = NULL;
	}
 
	m_cmAudioOpen -= cmMask;
	m_cmAudioCyclic -= cmMask;
 
	wPipeIndex = wPipeIndex;
	m_ProcessId[ wPipeIndex ] = NULL;
	m_Pipes[ wPipeIndex ].wInterleave = 0;
	
	ECHO_DEBUGPRINTF( ("CloseAudio - ECHOSTATUS_OK - m_cmAudioOpen 0x%x\n",m_cmAudioOpen.GetMask()) );
	return ECHOSTATUS_OK;
 
}	// ECHOSTATUS CEchoGals::CloseAudio
 
 
//===========================================================================
//
// VerifyAudioOpen is a utility function; it tells you if
// a pipe is open or not.
//
//===========================================================================
 
ECHOSTATUS CEchoGals::VerifyAudioOpen
(
	WORD		wPipeIndex
)
{
	CChannelMask	cmMask;
 
	if ( NULL == GetDspCommObject() || GetDspCommObject()->IsBoardBad() )
	{
		ECHO_DEBUGPRINTF( ("\tECHOSTATUS_DSP_DEAD\n") );
		return ECHOSTATUS_DSP_DEAD;
	}
 
	cmMask.SetIndexInMask( wPipeIndex );
	if ( !( m_cmAudioOpen.Test( &cmMask ) ) )
	{
		ECHO_DEBUGPRINTF( ("VerifyAudioOpen - ECHOSTATUS_CHANNEL_NOT_OPEN - wPipeIndex %d - m_cmAudioOpen 0x%x - cmMask 0x%x\n",
							wPipeIndex,m_cmAudioOpen.GetMask(),cmMask.GetMask()) );
		return ECHOSTATUS_CHANNEL_NOT_OPEN;
	}
 
	return ECHOSTATUS_OK;
	
}	// ECHOSTATUS CEchoGals::VerifyAudioOpen
 
 
//===========================================================================
//
// GetActivePipes tells you which pipes are currently active; that is, which 
// pipes are currently playing or recording.
//
//===========================================================================
 
ECHOSTATUS CEchoGals::GetActivePipes
(
	PCChannelMask	pChannelMask
)
{
	if ( NULL == GetDspCommObject() || GetDspCommObject()->IsBoardBad() )
		return ECHOSTATUS_DSP_DEAD;
 
	GetDspCommObject()->GetActivePipes( pChannelMask );
	return ECHOSTATUS_OK;
}	// void CEchoGals::GetActivePipes()
 
 
//===========================================================================
//
// Just like GetActivePipes, but this one tells you which pipes are currently
// open.
//
//===========================================================================
 
ECHOSTATUS CEchoGals::GetOpenPipes
(
	PCChannelMask	pChannelMask
)
{
	if ( NULL == GetDspCommObject() || GetDspCommObject()->IsBoardBad() )
		return ECHOSTATUS_DSP_DEAD;
 
	*pChannelMask = m_cmAudioOpen;
	return ECHOSTATUS_OK;
 
}	// void CEchoGals::GetOpenPipes()
 
 
 
 
/******************************************************************************
 
 Functions for setting audio formats and the sample rate
 
 ******************************************************************************/
 
//===========================================================================
//
// Validate an audio format.
//
// For comments on audio formats, refer to the definition of 
// ECHOGALS_AUDIOFORMAT.
// 
//===========================================================================
 
ECHOSTATUS CEchoGals::QueryAudioFormat
(
	WORD							wPipeIndex,
	PECHOGALS_AUDIOFORMAT	pAudioFormat
)
{
	ECHOSTATUS Status = ECHOSTATUS_OK;
 
	ECHO_DEBUGPRINTF( ("CEchoGals::QueryAudioFormat:\n") );
	
	//
	// If this pipe is open, make sure that this audio format
	// does not exceed the stored pipe width
	//
	WORD wInterleave = pAudioFormat->wDataInterleave;
	WORD wStoredPipeWidth = m_Pipes[ wPipeIndex ].wInterleave;
 
	if (0 != wStoredPipeWidth)
	{
		if (wInterleave > wStoredPipeWidth)
		{
			ECHO_DEBUGPRINTF(("CEchoGals::QueryAudioFormat - pipe was opened "
									"with a width of %d; interleave of %d invalid.\n",
									wStoredPipeWidth,
									pAudioFormat->wDataInterleave));
			return ECHOSTATUS_BAD_FORMAT;			
		}
	}
	
	//
	// Check for super interleave (i.e. interleave > 2)
	//
	if (wInterleave > 2)
	{
		//
		// Make sure the card is capable of super interleave
		//
		if (0 == (m_wFlags & ECHOGALS_ROFLAG_SUPER_INTERLEAVE_OK))
			return ECHOSTATUS_NO_SUPER_INTERLEAVE;
	
		//
		// Interleave must be even & data must be little endian
		//
		if (	(0 != pAudioFormat->byDataAreBigEndian) ||
			  	(0 != (wInterleave & 1)	)
			)
			return ECHOSTATUS_BAD_FORMAT;
		
		//
		// 16, 24, or 32 bit samples are required
		//	
		if ( 	(32 != pAudioFormat->wBitsPerSample) &&
				(24 != pAudioFormat->wBitsPerSample) &&
				(16 != pAudioFormat->wBitsPerSample) )
			return ECHOSTATUS_BAD_FORMAT;
		
					
		//
		// Make sure that this interleave factor on this pipe
		// does not exceed the number of pipes for the card
		//	
		WORD wMaxPipe;
		
		if (wPipeIndex >= GetNumPipesOut())
		{
			wMaxPipe = GetNumPipesIn();
			wPipeIndex = wPipeIndex - GetNumPipesOut();
		}
		else
		{
			wMaxPipe = GetNumPipesOut();
		}
		
		if ( (wPipeIndex + wInterleave) > wMaxPipe)
			return ECHOSTATUS_BAD_FORMAT;
		
		return ECHOSTATUS_OK;	
	}
	
	//
	// Check the interleave
	//
	if ( 	(1 != pAudioFormat->wDataInterleave) &&
			(2 != pAudioFormat->wDataInterleave) )
 
	{
		ECHO_DEBUGPRINTF(	("CEchoGals::QueryAudioFormat - interleave %d not allowed\n",
								pAudioFormat->wDataInterleave));
		return ECHOSTATUS_BAD_FORMAT;		
	}
	
	//
	//	If the big endian flag is set, the data must be mono or stereo interleave,
	// 32 bits wide, left justified data.  Only the upper 24 bits are used.
	//
	if (pAudioFormat->byDataAreBigEndian)
	{
		//
		// Must have 32 bits per sample
		//
		if (pAudioFormat->wBitsPerSample != 32)
		{
			ECHO_DEBUGPRINTF(("CEchoGals::QueryAudioFormat - Only 32 bits per"
									" sample supported for big-endian data\n"));
			return ECHOSTATUS_BAD_FORMAT;
		}
		
		//
		// Mono or stereo only
		//
		switch (pAudioFormat->wDataInterleave)
		{
 
#ifdef STEREO_BIG_ENDIAN32_SUPPORT
 
			case 1 :
			case 2 :
				break;
#else
 
			case 1 :
				break;
 
#endif				
			
			default :
				ECHO_DEBUGPRINTF(("CEchoGals::QueryAudioFormat - Interleave of %d"
										" not allowed for big-endian data\n",
										pAudioFormat->wDataInterleave));
				return ECHOSTATUS_BAD_FORMAT;
		}
		
		return ECHOSTATUS_OK;
	}
	
	//
	// Check bits per sample
	//
	switch ( pAudioFormat->wBitsPerSample )
	{
		case 8 :
		case 16 :
		case 24 :
		case 32 :
			break;
		
		default :
			ECHO_DEBUGPRINTF(
				("CEchoGals::QueryAudioFormat: No valid format "
				 "specified, bits per sample %d\n",
				 pAudioFormat->wBitsPerSample) );
			Status = ECHOSTATUS_BAD_FORMAT;
			break;
	}
 
	return Status;
 
}	// ECHOSTATUS CEchoGals::QueryAudioFormat
 
 
//===========================================================================
//
// SetAudioFormat sets the format of the audio data in host memory
// for this pipe.
//
//===========================================================================
 
ECHOSTATUS CEchoGals::SetAudioFormat
(
	WORD							wPipeIndex,
	PECHOGALS_AUDIOFORMAT	pAudioFormat
)
{
	ECHOSTATUS	Status;
 
	ECHO_DEBUGPRINTF( ("CEchoGals::SetAudioFormat: "
							 "for pipe %d\n",
							 wPipeIndex) );
 
	//
	// Make sure this pipe is open
	//
	Status = VerifyAudioOpen( wPipeIndex );
	if ( ECHOSTATUS_OK != Status )
		return Status;
 
	//
	// Check the format
	//
	Status = QueryAudioFormat( wPipeIndex, pAudioFormat );
	if ( ECHOSTATUS_OK != Status )
		return Status;
	
	//
	// Set the format
	//
	Status = GetDspCommObject()->SetAudioFormat( wPipeIndex, pAudioFormat );
	
	return Status;
	
}	// ECHOSTATUS CEchoGals::SetAudioFormat - single pipe
 
 
//===========================================================================
//
// This call lets you set the audio format for several pipes at once.
//
//===========================================================================
 
ECHOSTATUS CEchoGals::SetAudioFormat
(
	PCChannelMask				pChannelMask,
	PECHOGALS_AUDIOFORMAT	pAudioFormat
)
{
	WORD			wPipeIndex = 0xffff;
	ECHOSTATUS	Status;
 
	if ( NULL == GetDspCommObject() || GetDspCommObject()->IsBoardBad() )
	{
		ECHO_DEBUGPRINTF( ("\tECHOSTATUS_DSP_DEAD\n") );
		return ECHOSTATUS_DSP_DEAD;
	}
 
	for ( ; ; )
	{
		wPipeIndex = pChannelMask->GetIndexFromMask( ++wPipeIndex );
		if ( (WORD) ECHO_INVALID_CHANNEL == wPipeIndex )
			break;							// We be done!
 
		//
		// See if this pipe is open
		//
		if ( !( m_cmAudioOpen.TestIndexInMask( wPipeIndex ) ) )
		{
			ECHO_DEBUGPRINTF( ("CEchoGals::SetAudioFormat: "
									 "for pipe %d failed, pipe not open\n",
									 wPipeIndex) );
			return ECHOSTATUS_CHANNEL_NOT_OPEN;
		}
 
		//
		// See if the format is OK
		//
		ECHO_DEBUGPRINTF( ("CEchoGals::SetAudioFormat: "
								 "for pipe %d\n",
								 wPipeIndex) );
		Status = QueryAudioFormat( wPipeIndex, pAudioFormat );
		if ( ECHOSTATUS_OK != Status )
			return Status;
 
		//
		// Set the format for this pipe
		//
		Status = GetDspCommObject()->SetAudioFormat( wPipeIndex, pAudioFormat );
		if ( ECHOSTATUS_OK != Status )
			return Status;
	}
 
	return ECHOSTATUS_OK;
 
}	// ECHOSTATUS CEchoGals::SetAudioFormat - multiple pipes
 
 
 
//===========================================================================
//
// GetAudioFormat returns the current audio format for a pipe.
//
//===========================================================================
 
ECHOSTATUS CEchoGals::GetAudioFormat
(
	WORD							wPipeIndex,
	PECHOGALS_AUDIOFORMAT	pAudioFormat
)
{
	ECHO_DEBUGPRINTF( ("CEchoGals::GetAudioFormat: "
							 "for pipe %d\n",
							 wPipeIndex) );
 
	GetDspCommObject()->GetAudioFormat( wPipeIndex, pAudioFormat );
 
	return ECHOSTATUS_OK;
 
}	// ECHOSTATUS CEchoGals::GetAudioFormat
 
 
//===========================================================================
//
// This function does exactly what you think it does.
//
// Note that if the card is not set to internal clock (that is, the hardware
// is synced to word clock or some such), this call has no effect.
//
// Note that all of the inputs and outputs on a single card share the same 
// clock.
//
//===========================================================================
 
ECHOSTATUS CEchoGals::SetAudioSampleRate
(
	DWORD		dwSampleRate
)
{
	ECHOSTATUS	Status;
 
	ECHO_DEBUGPRINTF( ("CEchoGals::SetAudioSampleRate: "
							 "to %ld Hz\n",
							 dwSampleRate) );
						
	//
	// Check to see if the sample rate is locked
	//
	if ( 0 != (m_wFlags & ECHOGALS_FLAG_SAMPLE_RATE_LOCKED))
	{
			return ECHOSTATUS_OK;
	}
	else
	{
		Status = QueryAudioSampleRate( dwSampleRate );
		if ( ECHOSTATUS_OK != Status )
			return Status;
 
		if ( dwSampleRate == GetDspCommObject()->SetSampleRate( dwSampleRate ) )
		{
			m_dwSampleRate = dwSampleRate;
			return ECHOSTATUS_OK;
		}
	}
	return ECHOSTATUS_BAD_FORMAT;
	
}	// ECHOSTATUS CEchoGals::SetAudioSampleRate
 
 
//===========================================================================
//
// GetAudioSampleRate - retrieves the current sample rate for the hardware
//
//===========================================================================
 
ECHOSTATUS CEchoGals::GetAudioSampleRate
(
	PDWORD	pdwSampleRate
)
{
	ECHO_DEBUGPRINTF( ("CEchoGals::GetAudioSampleRate\n"));
 
	*pdwSampleRate = m_dwSampleRate;
 
	return ECHOSTATUS_OK;
 
}	// ECHOSTATUS CEchoGals::GetAudioSampleRate
 
 
 
 
/******************************************************************************
 
 Functions related to the scatter-gather list
 
 ******************************************************************************/
 
 
//===========================================================================
//
// Use the given CDaffyDuck object as the scatter-gather list for this pipe
//
//===========================================================================
 
ECHOSTATUS CEchoGals::SetDaffyDuck(WORD wPipeIndex, CDaffyDuck *pDuck)	
{
	m_DaffyDucks[wPipeIndex] = pDuck;
	
	return ECHOSTATUS_OK;
							
}	// SetDaffyDuck
 
 
 
 
//===========================================================================
//
// This method returns a pointer to the daffy duck for a pipe; the caller
// can then have direct access to the daffy duck object.
//
//===========================================================================
 
CDaffyDuck *CEchoGals::GetDaffyDuck(WORD wPipeIndex)
{
	ECHO_DEBUGPRINTF(("CEchoGals::GetDaffyDuck for pipe index %d\n",wPipeIndex));
 
	if (wPipeIndex >= GetNumPipes())
		return NULL;
	
	return m_DaffyDucks[wPipeIndex];
}	
 
 
 
/******************************************************************************
 
 Functions for starting and stopping transport
 
 ******************************************************************************/
 
//===========================================================================
//
//	Start transport for a single pipe
//
//===========================================================================
 
ECHOSTATUS CEchoGals::Start
(
	WORD	wPipeIndex
)
{
	CChannelMask	cmMask;
 
	cmMask.SetIndexInMask( wPipeIndex );
	return Start( &cmMask );
 
}	// ECHOSTATUS CEchoGals::Start
 
 
//===========================================================================
//
//	Start transport for a group of pipes
//
// This function includes logic to sync-start several pipes at once,
// according to the process ID specified when the pipe was opened.  This is
// included to work around a limitation of the Windows wave API so that
// programs could use multiple inputs and outputs and have them start at the
// same time.  
//
// If you don't want to use this feature, call CEchoGals::ClearFlags 
// with ECHOGALS_FLAG_SYNCH_WAVE and the pipes will start immediately.
//
//===========================================================================
 
ECHOSTATUS CEchoGals::Start
(
	PCChannelMask	pChannelMask
)
{
	WORD				wPipe;
	DWORD				dwPhysStartAddr;
	CChannelMask	cmStart;
	PVOID				ProcessId = NULL;
	CDspCommObject *pDCO;
	
	pDCO = GetDspCommObject();
	if ( NULL == pDCO || pDCO->IsBoardBad() )
		return ECHOSTATUS_DSP_DEAD;
	
	//
	//	See if we are dealing with synchronized wave pipes.  If the sync
	// flag is set, get the process ID for this pipe to compare with
	// other pipes.
	//
	if ( GetFlags() & ECHOGALS_FLAG_SYNCH_WAVE )
	{
		wPipe = pChannelMask->GetIndexFromMask( 0 );
		ProcessId = m_ProcessId[ wPipe ];
	}
	
	//--------------------------------------------------------
	// Process each pipe in the mask
	//--------------------------------------------------------
 
	for (wPipe = 0; wPipe < GetNumPipes(); wPipe++)
	{
		PDWORD pdwDspCommPositions;
 
		//
		// Skip this pipe if it's not in the mask
		//
		if (!pChannelMask->TestIndexInMask(wPipe))
			continue;
		
		//
		// This pipe must have a CDaffyDuck object
		//	
		if (NULL == m_DaffyDucks[ wPipe ])
		{
			ECHO_DEBUGPRINTF(("CDaffyDuck::Start - trying to start pipe index %d "
									"but there is no CDaffyDuck!\n",wPipe));
			return ECHOSTATUS_CHANNEL_NOT_OPEN;		
		}
		
		//
		// If this pipe was opened in cyclic mode, make sure that the corresponding
		// CDaffyDuck has been wrapped
		//	
		if (	(0 != m_cmAudioCyclic.TestIndexInMask( wPipe ) ) &&
				(FALSE == m_DaffyDucks[wPipe]->Wrapped()) 
			)
		{
			ECHO_DEBUGPRINTF(("CEchoGals::Start called for pipe index %d - "
									"pipe was opened in cyclic mode, but the duck "
									"has not been wrapped\n",wPipe));
			return ECHOSTATUS_DUCK_NOT_WRAPPED;
		}
 
		//
		// Set the physical start address for the duck for this pipe
		//
		dwPhysStartAddr = m_DaffyDucks[wPipe]->GetPhysStartAddr();
		pDCO->SetAudioDuckListPhys( wPipe, dwPhysStartAddr );		
		
				
		//
		// Do different things to this pipe depending on the 
		// state
		//	
		switch (m_byPipeState[wPipe])
		{
			case PIPE_STATE_RESET :
				//
				// Clean up the DMA position stuff
				//
				pdwDspCommPositions = pDCO->GetAudioPositionPtr();
				pdwDspCommPositions[ wPipe ] = 0;
				
				//
				// If this pipe isn't synced or is in a reset state,
				// start it up
				//
				if (NULL == ProcessId)
				{
					m_byPipeState[ wPipe ] = PIPE_STATE_STARTED;
					cmStart.SetIndexInMask( wPipe );
				}
				else
				{
					//
					// This pipe is synced; upgrade to PENDING
					//
					m_byPipeState[ wPipe ] = PIPE_STATE_PENDING;
				}
				break;
 
				
			case PIPE_STATE_STOPPED :
 
				if (NULL == ProcessId)
				{
					//
					// Non-synced pipe; start 'er up!
					//
					m_byPipeState[ wPipe ] = PIPE_STATE_STARTED;
					cmStart.SetIndexInMask( wPipe );
				}
				else
				{
					//
					// Synced pipe; if this pipe is in STOP mode, 
					// upgrade it to PENDING status
					//
					m_byPipeState[ wPipe ] = PIPE_STATE_PENDING;
				}
				break;
 
				
			case PIPE_STATE_PENDING :							
			case PIPE_STATE_STARTED :
				break;
		}
	}
		
	//-----------------------------------------------------------------
	// Start the pipes
	//-----------------------------------------------------------------
 
	//
	// Don't go if all the synced pipes are not yet pending
	//
	BOOL	fAllReady = TRUE;
	for ( wPipe = 0; wPipe < GetNumPipes(); wPipe++ )
	{
		if ( 	( ProcessId == m_ProcessId[ wPipe ] ) &&
				( PIPE_STATE_STOPPED == m_byPipeState[wPipe]))
		{
			ECHO_DEBUGPRINTF(("CEchoGals::Start - can't start; pipe %d "
									"still set to state %d\n",
									wPipe,
									m_byPipeState[wPipe]));
			fAllReady = FALSE;
		}
	}
	
	//
	// All synced pipes are pending; time to go!
	//
	if (fAllReady)
	{
		for (wPipe = 0; wPipe < GetNumPipes(); wPipe++)
		{
			if ( 	(ProcessId == m_ProcessId[ wPipe ]) &&
					(PIPE_STATE_PENDING == m_byPipeState[ wPipe ]))
			{
				m_byPipeState[wPipe] = PIPE_STATE_STARTED;
				cmStart.SetIndexInMask( wPipe );
				ECHO_DEBUGPRINTF(("CEchoGals::Start - setting pipe %d to start\n",
										wPipe));
			}
		}
	}
 
	if ( cmStart.IsEmpty() )
		return ECHOSTATUS_OK;
 
 
	//-----------------------------------------------------------------
	// Time to go
	//-----------------------------------------------------------------
	
	return pDCO->StartTransport( &cmStart );
	
}	// ECHOSTATUS CEchoGals::Start
 
 
 
//===========================================================================
//
// Stop a single pipe
//
//===========================================================================
 
ECHOSTATUS CEchoGals::Stop
(
	WORD	wPipeIndex
)
{
	CChannelMask	cmMask;
 
	cmMask.SetIndexInMask( wPipeIndex );
	return( Stop( &cmMask ) );
	
}	// ECHOSTATUS CEchoGals::Stop
 
 
//===========================================================================
//
// Stop several pipes simultaneously
//
//===========================================================================
 
ECHOSTATUS CEchoGals::Stop
(
	PCChannelMask	pChannelMask
)
{
	INT32			i;
	ECHOSTATUS	Status;
 
	if ( NULL == GetDspCommObject() || GetDspCommObject()->IsBoardBad() )
		return ECHOSTATUS_DSP_DEAD;
 
	Status = GetDspCommObject()->StopTransport( pChannelMask );
	if ( ECHOSTATUS_OK != Status )
		return Status;
 
	for ( i = 0; i < GetNumPipes(); i++ )
	{									
		//
		//	Skip channel if not in mask
		//
		if ( !pChannelMask->TestIndexInMask( (WORD) i ) )
			continue;
 
 
		//
		// Don't bother if it's stopped already
		//
		if ( PIPE_STATE_STOPPED == m_byPipeState[ i ] )
			continue;
 
		//
		// Muck with the DMA position
		//
		UpdateDmaPos( (WORD) i );
		
		m_byPipeState[ i ] = PIPE_STATE_STOPPED;
	}
 
	return Status;
 
}	// ECHOSTATUS CEchoGals::Stop
 
 
//===========================================================================
//
// Reset transport for a single pipe
//
//===========================================================================
 
ECHOSTATUS CEchoGals::Reset
(
	WORD	wPipeIndex
)
{
	CChannelMask	cmMask;
 
	cmMask.SetIndexInMask( wPipeIndex );
	return Reset( &cmMask );
	
}	// ECHOSTATUS CEchoGals::Reset
 
 
//===========================================================================
//
// Reset transport for a group of pipes simultaneously
//
//===========================================================================
 
ECHOSTATUS CEchoGals::Reset
(
	PCChannelMask	pChannelMask
)
{
	WORD			i;
	ECHOSTATUS	Status;
 
	if ( NULL == GetDspCommObject() || GetDspCommObject()->IsBoardBad() )
		return ECHOSTATUS_DSP_DEAD;
 
	Status = GetDspCommObject()->ResetTransport( pChannelMask );
	if ( ECHOSTATUS_OK != Status )
		return Status;
 
	for ( i = 0; i < GetNumPipes(); i++ )
	{
		//
		//	Skip channel if not in mask
		//
		if ( !pChannelMask->TestIndexInMask( (WORD) i ) )
			continue;
 
		if ( PIPE_STATE_RESET == m_byPipeState[ i ] )
			continue;
 
		//
		// Muck with the DMA position
		//
		UpdateDmaPos( i );
		m_dwLastDspPos[ i ] = 0;
		GetDspCommObject()->ResetPipePosition(i);
		
		m_byPipeState[ i ] = PIPE_STATE_RESET;
	}
	
	return Status;
 
}	// ECHOSTATUS CEchoGals::Reset
 
 
 
 
/******************************************************************************
 
 Functions for handling the current DMA position for pipes; the DMA position
 is the number of bytes transported by a pipe.
 
 ******************************************************************************/
 
//===========================================================================
//
// The DSP sends back a 32 bit DMA counter for each pipe of the number of bytes 
// transported; this count is written by the DSP to the comm page without
// the driver doing anything.
//
// The driver then maintains a 64 bit counter based off of the DSP's counter.
//
// Call UpdateDmaPos to cause the driver to update the internal 64 bit DMA
// counter.
//
// The 64 bit DMA counter is in units of bytes, not samples.
//
//===========================================================================
 
void CEchoGals::UpdateDmaPos( WORD wPipeIndex )
{
	DWORD dwDspPos;
	DWORD dwDelta;
	
	//
	// Get the current DSP position and find out how much it
	// has moved since last time.  This is necessary to avoid
	// the 32 bit counter wrapping around.
	//
	dwDspPos = GetDspCommObject()->GetAudioPosition( wPipeIndex );
	dwDelta = dwDspPos - m_dwLastDspPos[ wPipeIndex ];
	
	//
	// Adjust the 64 bit position
	//
	m_ullDmaPos[ wPipeIndex ] += dwDelta;
	m_dwLastDspPos[ wPipeIndex ] = dwDspPos;
 
} // UpdateDmaPos
 
 
//===========================================================================
//
// ResetDmaPos resets the 64 bit DMA counter.
//
//===========================================================================
 
void CEchoGals::ResetDmaPos(WORD wPipe)
{
	m_ullDmaPos[ wPipe ] = 0;
	m_dwLastDspPos[ wPipe ] = 0;
 
	//
	// There may still be mappings in the daffy duck; if so,
	// tell them to reset their DMA positions starting at zero
	//
	if (NULL != m_DaffyDucks[wPipe])
		m_DaffyDucks[wPipe]->ResetStartPos();
}
 
 
//===========================================================================
//
// This is a very powerful feature; calling this function gives you a pointer
// to the memory location where the DSP writes the 32 bit DMA position for
// a pipe.  The DSP is constantly updating this value as it moves data.
//
// Since the DSP is constantly updating it, you can dereference this pointer
// from anywhere and read the DMA position without calling the generic driver.
// This means that with some adroit mapping, you could read the DMA position
// from user mode without calling the kernel.
//
// Remember, Peter - with great power comes great responsibility; you should 
// only read this pointer and never write to it or to anywhere around it.  You 
// could easily lock up your computer.
//
// Note that the DSP writes the position in little endian format; if you are
// on a big endian machine, you will need to byte swap the postion
// before you use it.
//
//===========================================================================
 
ECHOSTATUS CEchoGals::GetAudioPositionPtr
(
	WORD		wPipeIndex,
	PDWORD &	pdwPosition
)
{
	if ( NULL == GetDspCommObject() || GetDspCommObject()->IsBoardBad() )
		return ECHOSTATUS_DSP_DEAD;
 
	if (wPipeIndex >= GetNumPipes())
	{
		pdwPosition = NULL;
		return ECHOSTATUS_INVALID_CHANNEL;
	}
		
	PDWORD	pdwDspCommPos = GetDspCommObject()->GetAudioPositionPtr();
 
	pdwPosition = pdwDspCommPos + wPipeIndex;
 
	return ECHOSTATUS_OK;
 
}	// ECHOSTATUS CEchoGals::GetAudioPositionPtr
 
 

V570 The 'wPipeIndex' variable is assigned to itself.