//===========================================================================
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997  Microsoft Corporation.  All Rights Reserved.
//
//===========================================================================
//
//	   filename: cdevcom.c
//
//     External Device Communication Functions
//
//---------------------------------------------------------
//
//	NOTES:
//	 1. Code is un-optimized for clarity
//	 2. Minimal error checking.
//	 3. Protocol is tested with SVO-5800 (RS-422) and SVBK-10 (RS-232) machines
//	 4. Timecode reading is supported, but dealing with multiple
//		calling threads is nasty, so be sure you know what you're
//		doing before you change anything.
//	 
//
//---------------------------------------------------------

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <streams.h>
#include <mmsystem.h>

#include "ctimecod.h"
#include "cdevcom.h" 

//----------------------------------------------------------
// structures

// SVO-5800/SVBK-1 Command Structure
typedef struct tagSV_CMD{
	BYTE bCmd1;		// Command byte 1 - lo nybble has data byte
					//  count
	BYTE bCmd2;		// Command byte 2
	BYTE bCmd3;		// Data if required by command, or checksum if none
	BYTE bCmd4;		// Checksum if 1 data byte
	BYTE bByteCnt;	// How many bytes in the command (including checksum)
	int iRespCnt;	// How many bytes in the response
} SV_CMD;

// SVO Commands - this is the command table.  
// ***IT MUST BE IN THE SAME ORDER AS THE COMMAND LIST IN CDEVCOM.H!!!!!
static SV_CMD SvoCmdTbl[] = {
	{0x20,1,0x21,0,3,3},		// Play
	{0x20,0,0x20,0,3,3},		// Stop
	{0x61,0x0c,0x11,0x7e,4,11},	// Request LTC
	{0x41,0x36,1,0x78,4,3},		// Timer Mode Select: LTC
	{0x20,0x20,0x40,0,3,3},   	// Rewind
	{0x20,0x6b,0x8b,0,3,3},		// Freeze on
	{0x20,0x6a,0x8a,0,3,3},		// Freeze off
	{0x20,0x04,0x24,0,3,3},		// Standby off
	{0x20,0x05,0x25,0,3,3},		// Standby on
	{0x00,0x11,0x11,0,3,5},		// Device Type Request
	{0x61,0x20,0x08,0x89,4,11},	// Status Request - 8 bytes
	{0x20,0x0f,0x2f,0,3,3},		// Eject
	{0x00,0x0c,0x0c,0,3,3},		// Local On
	{0x00,0x1d,0x1d,0,3,3},		// Local Off
	{0x20,0x10,0x30,0,3,3},		// Fast Forward
	{0x20,0x02,0x22,0,3,3},		// Record
	{0xFF,0,0,0,0,0}			// Device Clear
	};

// device type request table
typedef struct tagSVO_DEVTYPE{
	BYTE bData1;		// 1st data byte of response
	BYTE bData2;		// 2nd data byte of response
} SVO_DEVTYPE;

static SVO_DEVTYPE SvoDevTbl[] = {
	{0x80, 0x0c},
	{0x81, 0x0c},
	{0x80, 0x08},
	{0x80, 0x08},
	{0x81, 0x08},
	{0x10, 0x2c},
	{0x11, 0x2c},
	{0x10, 0x28},
	{0x11, 0x28},
	{0x10, 0x20},
	{0x11, 0x20},
	{0x10, 0x21},
	{0x11, 0x21},
	{0x20, 0x51}
};

const int DevTypeCnt = sizeof(SvoDevTbl);

const TCHAR *strSvoDevID[] = {
	"EVO-9850",
	"EVO-9850P",
	"EVO-9800",
	"EVO-9800A",
	"EVO-9800P",
	"SVO-9600",
	"SVO-9620",
	"SVP-9000",
	"SVP-9020",
	"SVO-5800",
	"SVO-5800P",
	"SVO-5600",
	"SVO-5600P",
	"UVW-1800",
};
const TCHAR *strUnknownDevID =
	{"Unknown Device"};

// SVBK Command Table
// ***IT MUST BE IN THE SAME ORDER AS THE COMMAND LIST IN CDEVCOM.H!!!!!
static SV_CMD SvbkCmdTbl[] = {
	{0x3A,0,0,0,1,1},		// Play
	{0x3F,0,0,0,1,1},		// Stop
	{0xA0,0,0,0,1,9},		// Request LTC
	{0xFF,0,0,0,0,0},		// Timer Mode Select: LTC
	{0xAC,0,0,0,1,1},   	// Rewind
	{0x4F,0,0,0,1,1},		// Freeze on
	{0x3A,0,0,0,1,1},		// Freeze off
	{0xFF,0,0,0,0,0},		// Standby off
	{0xFF,0,0,0,0,0},		// Standby on
	{0x8F,0,0,0,1,1},		// Device Type Request
	{0xD7,0,0,0,1,5},		// Status Request - 5 bytes
	{0x2A,0,0,0,1,2},		// Eject
	{0xFF,0,0,0,0,0},		// Local On
	{0xFF,0,0,0,0,0},		// Local Off
	{0xAB,0,0,0,1,1},		// Fast Forward
	{0xFA,0xFA,0xCA,0,3,1},	// Record
	{0x56,0,0,0,1,1}		// Device Clear
	};

const TCHAR *strSvbkDevID =
	{"SVBK-1"};
const int SvbkDevType = 0x80;

// which machine's command table are we using?
SV_CMD *pVcrCmdTable = SvbkCmdTbl;
int VcrType;				// 0 for SVO, 1 for SVBK
static TCHAR SvbkBuf[16];	// for multi-byte commands

// for simulation mode
static BYTE SvoAck[] = {		
	0x10,0x01,0x11};
static DWORD SvoAckCount = 3L;
static BYTE SvbkAck[] = {		
	0x0A};
static DWORD SvbkAckCount = 1L;
static TCHAR strDevID[] = "Pretend Machine";

static DWORD SimulatedFramecount = 0L;
static long lNDFTable[] = {1080000L,108000L,18000L,1800L,300L,30L,10L,1L};

// COM port stuff
#define COMM_TIMEOUT_MS = 60;

static TCHAR DevPortCom1[] = "COM1";
static TCHAR DevPortCom2[] = "COM2";
static TCHAR DevPortCom3[] = "COM3";
static TCHAR DevPortCom4[] = "COM4";
static TCHAR buf[20];

// init static members
HANDLE CDevCom::m_hDevPort = 0;			
TCHAR * CDevCom::m_pcDevPort = NULL;
BYTE CDevCom::m_bLastCmd = 0;          
BYTE CDevCom::m_LastMotionCmd = 0;
int CDevCom::m_CurCmdCookie = 1;
int CDevCom::m_LastSentCmdCookie = -1;
DCB CDevCom::m_dcb = {
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0};
	
int CDevCom::m_PendingResponseCnt = 0;
BOOL CDevCom::m_bSimulateHardware = FALSE;
COMMTIMEOUTS CDevCom::m_ctmoTimeout = {
	0,0,0,0,0};
COMSTAT CDevCom::m_csCommStat = {
	0L,0L,0L,0L,0L,0L,0L,0L,0L,0L,};
// Command Queue stuff to deal with multi-threading
#define COMMANDLISTSIZE 10
#define RESPONSE_SLEEP_TIME 5	// in milliseconds
#define RESPONSE_RETRY_MAX	20	// should never have to wait for more than this
								// numer of sleep times
// Use Generic list template class to build command queue. Use of
// such a queue will allow the communications class to be thread safe.
static CGenericList<COMMANDOBJECT> CommandList("List of Pending Commands");

//intialize an empty array of command objects
COMMANDOBJECT CommandObjects[] = {
	{-1,-1, 0xffff},
	{-1,-1, 0xffff},
	{-1,-1, 0xffff},
	{-1,-1, 0xffff},
	{-1,-1, 0xffff},
	{-1,-1, 0xffff},
	{-1,-1, 0xffff},
	{-1,-1, 0xffff},
	{-1,-1, 0xffff},
	{-1,-1, 0xffff}
};

// initialize an empty array of response objects - we don't create a linked 
// list of these because we don't expect to every have more that two or three
// buffered responses
RESPONSEOBJECT ResponseObject[] = {
	{ -1, -1, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{ -1, -1, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{ -1, -1, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
#define MAX_RESPONSE_OBJECTS 3

//---------------------------------------------------------
//
// CDevCom constructor
//
//---------------------------------------------------------
CDevCom::CDevCom(CBaseFilter *pFilter, HRESULT *phr)
{
	m_pFilter = pFilter;
	// need some help with timecode
	m_pTimecode = new CTimecode();
	if (m_pTimecode == NULL) {
        *phr = E_OUTOFMEMORY;
       return;
	}
	*phr = S_OK;
	return;
	
}
//---------------------------------------------------------
//
// Destructor
//
//---------------------------------------------------------
CDevCom::~CDevCom()
{

}

//---------------------------------------------------------
//
//	OpenDevPort 
//
//	Tries to open the requested port.  If port is not available,
//	enters "simulation" mode
//
//---------------------------------------------------------
DWORD 
CDevCom::OpenDevPort( int Port )
{
	TCHAR resp[20];
	DWORD LastError;
	DWORD dwCommError;
	int Cookie;

	timeBeginPeriod(1);	// setup 1ms timer for timestamping
	m_bSimulateHardware = FALSE;	// this may have been set TRUE previously
	// get the port
	switch (Port){
		case DEV_PORT_COM1:
			m_pcDevPort = DevPortCom1;
			break;
		case DEV_PORT_COM2:
			m_pcDevPort = DevPortCom2;
			break;
		case DEV_PORT_COM3:
			m_pcDevPort = DevPortCom3;
			break;
		case DEV_PORT_COM4:
			m_pcDevPort = DevPortCom4;
			break;
		case DEV_PORT_SIM:
		case DEV_PORT_DIAQ:
		case DEV_PORT_ARTI:
		default:
			if (m_hDevPort)
				CloseDevPort();
			m_bSimulateHardware = TRUE;
			return 0L;
	}
	// open the port
	if ( (m_hDevPort = CreateFile( m_pcDevPort,	GENERIC_READ | GENERIC_WRITE,
    				0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
    				NULL )) == (HANDLE) -1 ){
		// we're in simulation mode
		DWORD dwTemp = GetLastError();
		m_bSimulateHardware = TRUE;
		return 1L;
	}

	// get the Device Control Block
	if ( !GetCommState( m_hDevPort, &m_dcb ) ) {
		CloseDevPort();
		m_bSimulateHardware = TRUE;
   		return 1L;
	}

	// put in our values
	m_dcb.BaudRate = 38400;
    m_dcb.fDtrControl = DTR_CONTROL_ENABLE;
    m_dcb.fDsrSensitivity = FALSE;
    m_dcb.fOutX = FALSE;
    m_dcb.fInX = FALSE;
    m_dcb.fNull = FALSE;
    m_dcb.fRtsControl = RTS_CONTROL_ENABLE;
    m_dcb.fAbortOnError = TRUE;
  	m_dcb.ByteSize = 8;
    m_dcb.Parity = ODDPARITY;
    m_dcb.StopBits = ONESTOPBIT;

	if ( !SetCommState(m_hDevPort, &m_dcb) ){
		// failure forces simulation mode
		m_bSimulateHardware = TRUE;
		return 1L;
	}

	// set the comm timeout
	if ( !GetCommTimeouts(m_hDevPort, &m_ctmoTimeout) )	{
		CloseDevPort();
		m_bSimulateHardware = TRUE;
   		return 1L;
	}
	// timeout after COMM_TIMEOUT_MS milliseconds
	m_ctmoTimeout.ReadIntervalTimeout = 15L;
	m_ctmoTimeout.ReadTotalTimeoutMultiplier = 10L;
	m_ctmoTimeout.ReadTotalTimeoutConstant = 60L;
	m_ctmoTimeout.WriteTotalTimeoutMultiplier = 1L;
	m_ctmoTimeout.WriteTotalTimeoutConstant = 60L;
	if ( !SetCommTimeouts(m_hDevPort, &m_ctmoTimeout) )	{	
		CloseDevPort();
		m_bSimulateHardware = TRUE;
   		return 1L;
	}

	// now let's see if there is some hardware there
	// try an SVO type machine first
	pVcrCmdTable = SvoCmdTbl;
	VcrType = 0;
	if (SendDevCmd(DEVICE_TYPE_REQUEST, &Cookie) != 0L)	{	
		GetDevResponse(resp, Cookie);	//cleanup
		CloseDevPort();
		m_bSimulateHardware = TRUE;
		m_PendingResponseCnt = 0;
   		return 1L;
	}

	if ( GetDevResponse(resp, Cookie) != 0L){
		// try the SVBK now
		pVcrCmdTable = SvbkCmdTbl;
		VcrType = 1;
		FlushDevResponseBuffer();	// clean out the UART
		ClearCommError( m_hDevPort, &dwCommError, &m_csCommStat );
		// need to change the baud rate among other things
		m_dcb.BaudRate = 9600;
		m_dcb.Parity = NOPARITY;
    	if ( !SetCommState(m_hDevPort, &m_dcb) ){
			// set simulation mode
			LastError = GetLastError();
			m_bSimulateHardware = TRUE;
			return 1L;
		}
		Sleep(30L);	// give the machine time to recover
		SendDevCmd(DEVICE_CLEAR, &Cookie);
		GetDevResponse(resp, Cookie);
		// DEVICE_CLEAR puts the machine in pause, so put it in stop
		SendDevCmd(STOP, &Cookie);
		GetDevResponse(resp, Cookie);
		Sleep(30L);					// give the machine time to recover
		FlushDevResponseBuffer();	// clean out the UART
		ClearCommError( m_hDevPort, &dwCommError, &m_csCommStat );
		if (SendDevCmd(DEVICE_TYPE_REQUEST, &Cookie) != 0L)	{
			GetDevResponse(resp, Cookie);	//cleanup
			CloseDevPort();
			m_bSimulateHardware = TRUE;
			m_PendingResponseCnt = 0;
   			return 1L;
		}
		// we don't actually check the response value here because
		// it is not always consistant.
		if ( GetDevResponse(resp, Cookie) != 0L){
			CloseDevPort();
			m_bSimulateHardware = TRUE;
			return 1L;
		}
		// cleanup
		ClearCommError( m_hDevPort, &dwCommError, &m_csCommStat );
		FlushDevResponseBuffer();	// clean out the UART
		return 0L;
	}
	 return 0L;
}

//---------------------------------------------------------
//
//	CloseDevPort
//
//	Shuts things down
//
//---------------------------------------------------------
DWORD 
CDevCom::CloseDevPort( void )
{
	DWORD LastError;

	timeEndPeriod(1);	// restore timer period

	if ( m_hDevPort && !m_bSimulateHardware )
	{	// close the port
		if (DeleteFile(m_pcDevPort)) {
			LastError = GetLastError();
			return DEV_COMM_ERR_COMMUNICATION_ERROR;
		}
		m_pcDevPort = NULL;
		if ( !CloseHandle( m_hDevPort ) ) {
			LastError = GetLastError();
			return DEV_COMM_ERR_COMMUNICATION_ERROR;
		}
		m_hDevPort = 0;
		return 0L;
	}
	m_bSimulateHardware = FALSE;
	return 0L;
}

//---------------------------------------------------------
//
//	FlushDevResponseBuffer
//
//	A cleanup method
//
//---------------------------------------------------------
DWORD 
CDevCom::FlushDevResponseBuffer( void )
{
	DWORD LastError;
	DWORD dwRetByteCnt;
	DWORD dwCommError;
	char buf[16];

	if (m_bSimulateHardware)
		return 0L;
	m_PendingResponseCnt = 0;
	if (PurgeComm(m_hDevPort, PURGE_RXCLEAR)) {
		LastError = GetLastError();
		return DEV_COMM_ERR_COMMUNICATION_ERROR;
	}
	ClearCommError( m_hDevPort, &dwCommError, &m_csCommStat );
	if (m_csCommStat.cbInQue) {
		if ( !ReadFile( m_hDevPort, buf, (DWORD)m_csCommStat.cbInQue,
		 (LPDWORD)&dwRetByteCnt, (LPOVERLAPPED)NULL ) ){
			LastError = GetLastError();
			// could stand a bit better error handling here
			return DEV_COMM_ERR_COMMUNICATION_ERROR;
		}
	}
			
	return 0L;
}

//---------------------------------------------------------
//
//	SendDevCmd
//
//	Sends command to device (or queues it up for xmission).
//	Returned a Cookie so the caller can find the
//	correct response
//					
//---------------------------------------------------------
DWORD
CDevCom::SendDevCmd( int Cmd, int *pCookie)
{
	CAutoLock lock(this);
	PCOMMANDOBJECT pCmdObj;
	DWORD dwret;

	// return immediately if command is not supported
	if (pVcrCmdTable[Cmd].bCmd1 == 0xFF)
		return DEV_COMM_ERR_COMMAND_NOT_SUPPORTED;

	// get a new cookie
	if (m_CurCmdCookie++ == 0)
			m_CurCmdCookie++;
	// queue the command if the response has not yet been
	// pulled for the previously sent command
	if (m_PendingResponseCnt){
		// some extra instructions here for debugging
		pCmdObj = AddCommand(Cmd, m_CurCmdCookie);
		if (pCmdObj == NULL){
			ASSERT(pCmdObj);
			return DEV_COMM_ERR_QUEUE_OVERFLOW;
		}
		*pCookie = m_CurCmdCookie;
		return 0L;
	}
	dwret = ReallySendDevCmd( Cmd, m_CurCmdCookie);
	*pCookie = m_CurCmdCookie;
	return dwret;
}

//---------------------------------------------------------
//
//	ReallySendDevCmd
//
//	This does the actual sending of queued commands
//
//---------------------------------------------------------
DWORD
CDevCom::ReallySendDevCmd( int Cmd, int Cookie )
{
	SV_CMD *svCommand;
	BYTE bCmdSequenceCnt;
	DWORD dwBytesWritten;
	DWORD LastError;
	DWORD dwret = 0L;
	TCHAR *pCmd;
	int i;

	m_bLastCmd = Cmd;
	if ( m_isMotionCmd(Cmd) )	// do we still need to know this?
		m_LastMotionCmd = Cmd;
	
	if (!m_bSimulateHardware) {
		// get the command from the table and shoot it out
		svCommand = &pVcrCmdTable[Cmd];
		// we handle the two machine types differently because the SVBK
		// machine might have multiple command/response pair sequences
		if (VcrType == 0) {	// SVO has one sequence and can send multiple bytes
			i = (int)svCommand->bByteCnt;
			bCmdSequenceCnt = 1;
		}
		else {	// SVBK only sends out one byte per sequence
			i = 1;
			bCmdSequenceCnt = svCommand->bByteCnt;
		}
		pCmd = (TCHAR *)svCommand;
		while (bCmdSequenceCnt) {	// do this loop for each sequence
			// send out the command
			if ( !WriteFile( m_hDevPort, (TCHAR *)pCmd, i,
					(LPDWORD)&dwBytesWritten, NULL ) ) {
					LastError = GetLastError();
					dwret = DEV_COMM_ERR_COMMUNICATION_ERROR;
			}
			if (VcrType == 1 && bCmdSequenceCnt > 1) {
				// must pick up response byte if another sequence is pending
				m_PendingResponseCnt++;
				ReallyGetDevResponse(SvbkBuf,1);
				// point to the next command byte in the sequence
				pCmd++;
				// special handling for SVBK's Eject command
				if (svCommand->bCmd1 == 0x2a)
					Sleep(1000);	// wait while EJECT completes
			}
			bCmdSequenceCnt--;
		}
	}
	m_LastSentCmdCookie = Cookie;
	m_PendingResponseCnt++;
	return dwret;
}

//---------------------------------------------------------
//
//	GetDevResponse
//
//	Returns device response to command.  If command
//	hasn't really been sent yet (still in queue), error
//	is returned in the DWORD
//
//---------------------------------------------------------
DWORD 
CDevCom::GetDevResponse( TCHAR *buf, int Cookie )
{
	CAutoLock lock(this);
	
	DWORD dwret = 0L;
	int temp;
	PCOMMANDOBJECT ptemp;
	POSITION posNextCommand;
	BOOL bNeedCommand = FALSE;
	
	// is this command's response buffered?
	if ( IsResponseBuffered(Cookie, &temp) ) {
		GetBufferedResponse( buf, Cookie );
		// don't send another command out since
		// a response wasn't cleared out of the COMM port
		return dwret;
	}

	// was this the last command sent?
	if (Cookie == m_LastSentCmdCookie) {
		// get the response
		dwret = ReallyGetDevResponse( buf, pVcrCmdTable[m_bLastCmd].iRespCnt );
		if (dwret)
			return dwret;
	}
	else {
		// since it wasn't, is a response pending?
		if (m_PendingResponseCnt) {
			dwret = BufferResponse();
			if (dwret)
				return dwret;
		}
		bNeedCommand = TRUE;
	}
	
	// since the command hasn't been sent it must be in the queue, 
	// so send something
	temp = CommandList.GetCount();
	if (temp) {
		posNextCommand = CommandList.GetHeadPosition();
		ptemp = CommandList.Get(posNextCommand);
		dwret = ReallySendDevCmd( ptemp->Command, ptemp->Cookie );
		ptemp = CommandList.RemoveHead();
		RemoveCommand(ptemp);
		return DEV_COMM_ERR_COMMAND_NOT_SENT_YET;
	}
	else 
		if (bNeedCommand)
			return DEV_COMM_ERR_COMMAND_MISSING;

	return dwret;	
}
//---------------------------------------------------------
//
//	ReallyGetDevResponse
//
//	Actually handles communications to get device response
//
//---------------------------------------------------------
DWORD 
CDevCom::ReallyGetDevResponse( TCHAR *buf, int bytecnt )
{
	DWORD dwRetByteCnt; 
	DWORD LastError;

	m_PendingResponseCnt--;
	if (m_bSimulateHardware) {
		(GetSimulatedResponse(buf));
	}
	else {
		if ( !ReadFile( m_hDevPort, buf, (DWORD)bytecnt,
			 (LPDWORD)&dwRetByteCnt, (LPOVERLAPPED)NULL ) ){
			LastError = GetLastError();
			// could stand a bit better error handling here
			return DEV_COMM_ERR_COMMUNICATION_ERROR;
		}
		if ( dwRetByteCnt != (DWORD)bytecnt ) {
			return DEV_COMM_ERR_RESPONSE_MISMATCH;
		}
		// SVBK doesn't use checksums and its timecode reading 
		// is a bit unpredictable
		if (VcrType == 0) {
			if ( TestResponse(buf, dwRetByteCnt) )
				return DEV_COMM_ERR_RESPONSE_CHECKSUM;
		}
		else {
			// sometimes an extra wierd character is sent
			// during timecode reading, so we need to watch for it
			if (bytecnt == pVcrCmdTable[READ_TC].iRespCnt) {
				// this was a timecode response, so check for multiple
				// 0xA1's at the beginning of the buffer.  If we have
				// more than one, we need to get another byte out of
				// the receive buffer
				if (buf[1] == (TCHAR)0xA1) {
					if ( !ReadFile( m_hDevPort, 
						&buf[pVcrCmdTable[READ_TC].iRespCnt],
						1L,	(LPDWORD)&dwRetByteCnt, (LPOVERLAPPED)NULL ) ){
						LastError = GetLastError();
						// could stand a bit better error handling here
							return DEV_COMM_ERR_COMMUNICATION_ERROR;
					}
					if ( dwRetByteCnt != 1L )
						return DEV_COMM_ERR_RESPONSE_MISMATCH;
				}
			}
		}
	}
	return S_OK;
}
//---------------------------------------------------------
//
//	BufferResponse
//
//	Pulls a response out of the COMM device for the most
//	recently send command
//
//---------------------------------------------------------
DWORD 
CDevCom::BufferResponse(void)
{
	int i; 
	// get an available slot in the array
	for (i=0; i<MAX_RESPONSE_OBJECTS; i++) {
		if (ResponseObject[i].Cookie == -1)
			break;
	}
	if (i>=MAX_RESPONSE_OBJECTS)
		return DEV_COMM_ERR_RESPONSE_OVERFLOW;
	// put in the cookie and response byte count
	ResponseObject[i].Cookie = m_LastSentCmdCookie;
	ResponseObject[i].ResponseByteCount = pVcrCmdTable[m_bLastCmd].iRespCnt;
	// get the response
	return ReallyGetDevResponse( ResponseObject[i].buf,
		ResponseObject[i].ResponseByteCount );
}
//---------------------------------------------------------
//
//	GetBufferedResponse
//
//	Pulls a response out of the response array
//
//---------------------------------------------------------
DWORD 
CDevCom::GetBufferedResponse( TCHAR *buf, int Cookie)
{
	int i; 
	
	if ( !IsResponseBuffered( Cookie, &i ) )
		return DEV_COMM_ERR_RESPONSE_MISSING;
	
	// copy the response into the return buffer
	memcpy( buf, ResponseObject[i].buf, ResponseObject[i].ResponseByteCount);
		
	// Clear the entry
	ResponseObject[i].Cookie = -1;
	ResponseObject[i].ResponseByteCount = -1;
	// get the response
	return S_OK;
}

//---------------------------------------------------------
//
//	IsResponseBuffered
//
//	Searches the response array for the given cookie
//
//---------------------------------------------------------
BOOL
CDevCom::IsResponseBuffered( int Cookie, int *index)
{
	int i; 
	// find the response in the array
	for (i=0; i<MAX_RESPONSE_OBJECTS; i++) {
		if (ResponseObject[i].Cookie == Cookie) {
			*index = i;
			return TRUE;
		}
	}
	return FALSE;
}

//---------------------------------------------------------
//
//	Wait4DevResponse
//
//	Wrapper method that handles retries
//
//---------------------------------------------------------
DWORD
CDevCom::Wait4DevResponse( TCHAR *buf, int Cookie )
{
	DWORD dwret;
	int i = 0;
	do {
		if (i > RESPONSE_RETRY_MAX)
			return DEV_COMM_ERR_RESPONSE_TIMEOUT;
		dwret = GetDevResponse(buf, Cookie);
		if (dwret != 0L && dwret != DEV_COMM_ERR_COMMAND_NOT_SENT_YET)
			return dwret;
		if (dwret) 
			Sleep(RESPONSE_SLEEP_TIME);
	}while (dwret);
	return dwret;
}
//---------------------------------------------------------
//
//	GetSimulatedResponse
//
//	Used for simulation mode
//
//---------------------------------------------------------
DWORD 
CDevCom::GetSimulatedResponse( TCHAR *buf )
{
	switch (m_bLastCmd)	{
		case READ_TC:
			if (m_LastMotionCmd == PLAY || m_LastMotionCmd == FREEZE_OFF) {
				SimulatedFramecount++;
				if ( SimulatedFramecount > 2591999L )
					SimulatedFramecount = 0;
			}
			else if (m_LastMotionCmd == REWIND)	{
				if (SimulatedFramecount >2591999L)
					SimulatedFramecount = 2591999L;
				else
					SimulatedFramecount-=10;
			}
			GetSimulatedTimecode(buf, SimulatedFramecount);
			return 0L;

		case DEVICE_TYPE_REQUEST:
			strcpy(buf, strDevID);
			return 0L;

		case PLAY:
	 	case STOP:
		case TIMER_MODE_SELECT_LTC:
		case REWIND:
		case FREEZE_ON:
		case FREEZE_OFF:
		case STANDBY_OFF:
		default:
			if (VcrType == 0)
				strncpy(buf, (TCHAR *)SvoAck, SvoAckCount);
			else
				strncpy(buf, (TCHAR *)SvbkAck, SvbkAckCount);
			return 0L;
	}
	return 0L;
}
//---------------------------------------------------------
//
//	m_isMotionCmd
//
//	private helper method to differentiate Motion commands
//	from things like Timecode requests (sometimes timecode
//	requests need to be handled differently)
//
//---------------------------------------------------------
BOOL
CDevCom::m_isMotionCmd( BYTE Cmd ) {
 	switch (Cmd) {
		case READ_TC:
		case TIMER_MODE_SELECT_LTC:
			return FALSE;
		case PLAY:
		case STOP:
		case REWIND:
		case FFWD:
		case RECORD:
		case FREEZE_ON:
		case FREEZE_OFF:
		case STANDBY_OFF:
		case EJECT:
			return TRUE;
		default:
			return FALSE;
	}
}
//---------------------------------------------------------
//
//	ProcessDeviceTypeResponse
//
// Convert response to device type request to string if possible
//
//---------------------------------------------------------
void
CDevCom::ProcessDeviceTypeResponse( TCHAR *bufin, TCHAR *bufout)
{
	int i;
	if (m_bSimulateHardware) {
		strcpy( bufout, bufin );
		return;
	}

	// handle the two machine types differently
	if (VcrType == 0) {
		// compare the 2 data bytes of the response to the list of known
		// machine types.  If we find a match, return the corresponding 
		// device type string
		for (i=0; i<DevTypeCnt; i++) {
			if ( bufin[2] != SvoDevTbl[i].bData1 )
				continue;
			if ( bufin[3] != SvoDevTbl[i].bData2 )
				continue;
			strcpy( bufout, strSvoDevID[i] );
			return;
		}
		// unknown machine
		strcpy( bufout, strUnknownDevID );
	}
	else		
		if (bufin[0] & (TCHAR)SvbkDevType)
			strcpy( bufout, strSvbkDevID );
		else
			// unknown machine
			strcpy( bufout, strUnknownDevID );
	return;
}

//---------------------------------------------------------
//
//	ProcessDeviceStatusResponse
//
// Stuff status structure with response to status request
//	returns FALSE if SVBK returns a NAK
//
//---------------------------------------------------------
BOOL
CDevCom::ProcessDeviceStatusResponse( TCHAR *bufin, PVCRSTATUS pStatus )
{
	if (m_bSimulateHardware) {
		pStatus->bCassetteOut = OAFALSE;
		pStatus->bLocal = OAFALSE;
		return FALSE;
	}

	// we only process two bits of information right now, and 
	// it's hardcoded for clarity and simplicity
	if (VcrType == 0) {	// SVO
		if (bufin[2] & 0x20)
			pStatus->bCassetteOut = OATRUE;
		else
			pStatus->bCassetteOut = OAFALSE;

		if (bufin[2] & 0x01)
			pStatus->bLocal = OATRUE;
		else
			pStatus->bLocal = OAFALSE;
	}
	else { // SVBK
		if (bufin[0] == 0x0b)	// machine can send a NAK if not ready
			return TRUE;
		if (bufin[0] & 0x08)
			pStatus->bCassetteOut = OATRUE;
		else 
			pStatus->bCassetteOut = OAFALSE;
			// dummy response for remote/local because that 
			// info is not available
		pStatus->bLocal = OAFALSE;
	}
	return FALSE;
}
//---------------------------------------------------------
//
//	AddCommand
//
// Insert pending command in empty space in array
//
//---------------------------------------------------------
PCOMMANDOBJECT
CDevCom::AddCommand( int Cmd, int Cookie ){

	int i;
	POSITION pos;
	
	for (i=0; i<COMMANDLISTSIZE; i++) {
		if (CommandObjects[i].Command == -1) {
			CommandObjects[i].Command = Cmd;
			CommandObjects[i].Cookie = Cookie;
			CommandObjects[i].Timestamp = timeGetTime();
			pos = CommandList.AddTail( &CommandObjects[i] );
			return &CommandObjects[i];
		}
	}
	return NULL; //Null means no more space
}
//---------------------------------------------------------
//
//	RemoveCommand
//
// Clear entry in command array
//
//---------------------------------------------------------
void
CDevCom::RemoveCommand( PCOMMANDOBJECT pCmdObj )
{
	pCmdObj->Command = -1;
	pCmdObj->Cookie = -1;
	return;
}
//---------------------------------------------------------
//
//	TestResponse
//
// Calculates checksum on response and compares it to embedded
// checksum.  Returns FALSE if OK, TRUE if not
//
//---------------------------------------------------------
BOOL
CDevCom::TestResponse( TCHAR *buf, int Count )
{
	BYTE CalcCheckSum = 0;
	int i;

	for ( i=0; i<(Count-1); i++) {
		CalcCheckSum += (BYTE)buf[i];
	}
	if ( CalcCheckSum != (BYTE)buf[(Count-1)] )
		return TRUE;
	return FALSE;
}
//---------------------------------------------------------
//
//	ProcessVcrTC - converts the timecode response to a binary
//	framecount.  Handles both SVO and SVBK timecode formats
//
//---------------------------------------------------------
DWORD
CDevCom::ProcessVcrTC( TCHAR *inbuf, DWORD *dwFCM, long *ptimecode, 
					  long *puserbits)
{
	BYTE btemp;
	WORD wtemp;
	static TCHAR outbuf[12];
	static TIMECODE_SAMPLE tcsTimecode;
	int i=0;
	TCHAR *ptemp;

	// look at first byte to determine SVO or SVBK
	if ((inbuf[0] & 0x70) != 0x70 ) { // SVO's command value
		// SVBK timecode format is 8 byte ASCII, Hx10 first
		// hours.  Can't tell whether it's drop or nondrop, 
		// so we'll stay nondrop SVBK also has an unpredictable 
		// number of 0xA1's at the head of the buffer, so we must correct.
		while ( inbuf[i] == (TCHAR)0xA1 || inbuf[i] == (TCHAR)0xA2 ) {
			i++;
		}
		ptemp =&inbuf[i];

		wtemp = AM_TIMECODE_30NONDROP;
		// hours
		outbuf[0] = ptemp[0];
		outbuf[1] = ptemp[1];
		outbuf[2] = ':';

		// minutes
		outbuf[3] = ptemp[2];
		outbuf[4] = ptemp[3];
		outbuf[5] = ':';

		// seconds
		outbuf[6] = ptemp[4];
		outbuf[7] = ptemp[5];
		outbuf[8] = ':';

		// frames
		outbuf[9] = ptemp[6];
		outbuf[10] = ptemp[7];

		// terminate
		outbuf[11] = 0;

		// convert to binary
		tcsTimecode.timecode.wFrameRate = wtemp;
		m_pTimecode->ConvertStringToTimecode(outbuf, &tcsTimecode); 
		*ptimecode = tcsTimecode.timecode.dwFrames;
		*dwFCM = (DWORD)wtemp;
		*puserbits = (DWORD)0L; // forget userbits
	}
	else { // SVO
		// SVO t/c format is this:
		// 		BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0
		//DATA1	 x	  DF	 10F   |		 1F
		//DATA2	 x		 10S	   |		 1S
		//DATA3	 x		 10M	   |		 1M
		//DATA4	 x	  x		 10H   |		 1H
		//
		// It lives in bytes 2 thru 5 in the response from the machine, and
		// userbits live in bytes 6 thru 9

		// just yank em out and put em in the output buffer
		// get the dropframe flag
		if ( inbuf[2] & 0x40 )
			wtemp = AM_TIMECODE_30DROP;
		else
			wtemp = AM_TIMECODE_30NONDROP;

		// hours
		btemp = inbuf[5] & 0x30;
		btemp = btemp>>(4*sizeof(TCHAR));
		btemp += '0';
		outbuf[0] = btemp;
		outbuf[1] = (inbuf[5] & 0x0f) + '0';
		outbuf[2] = ':';

		// minutes
		btemp = inbuf[4] & 0x70;
		btemp = btemp>>(4*sizeof(TCHAR));
		outbuf[3] = btemp + '0';
		outbuf[4] = (inbuf[4] & 0x0f) + '0';
		outbuf[5] = ':';

		// seconds
		btemp = inbuf[3] & 0x70;
		btemp = btemp>>(4*sizeof(TCHAR));
		outbuf[6] = btemp + '0';
		outbuf[7] = (inbuf[3] & 0x0f) + '0';
		if ( AM_TIMECODE_30DROP == wtemp )
			outbuf[8] = ';';
		else
			outbuf[8] = ':';

		// frames
		btemp = inbuf[2] & 0x30;
		btemp = btemp>>(4*sizeof(TCHAR));
		outbuf[9] = btemp + '0';
		outbuf[10] = (inbuf[2] & 0x0f) + '0';

		// terminate
		outbuf[11] = 0;

		// convert to binary
		tcsTimecode.timecode.wFrameRate = wtemp;
		m_pTimecode->ConvertStringToTimecode(outbuf, &tcsTimecode); 
		*ptimecode = tcsTimecode.timecode.dwFrames;
		*dwFCM = (DWORD)wtemp;
		*puserbits = (DWORD)inbuf[6];
	}
	return 0;
}

//---------------------------------------------------------
//
//	For no-hardware mode
//
//---------------------------------------------------------
void
CDevCom::GetSimulatedTimecode( TCHAR * timecode, long framecount )
{
	long temp;
	int i;

	if (VcrType == 0) {
		// SVO t/c format is this:
		// 		BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0
		//DATA1	 x	  DF	 10F   |		 1F
		//DATA2	 x		 10S	   |		 1S
		//DATA3	 x		 10M	   |		 1M
		//DATA4	 x	  x		 10H   |		 1H
		//
		// It lives in bytes 2 thru 5 in the response from the machine
		// Bytes 6 thru 9 are user bits, and 10 is the checksum
		
		// put in the command
		timecode[0] = 0x74;
		timecode[1] = 0x04;

		// convert the frame count to Svo t/c format
		temp = framecount / lNDFTable[0];	// hours
		framecount -= temp * lNDFTable[0];
		timecode[5] = (TCHAR)temp << (4*sizeof(TCHAR));
		temp = framecount / lNDFTable[1];
		framecount -= temp * lNDFTable[1];
		timecode[5] |= temp;	

		temp = framecount / lNDFTable[2];	// minutes
		framecount -= temp * lNDFTable[2];
		timecode[4] = (TCHAR)temp << (4*sizeof(TCHAR));
		temp = framecount / lNDFTable[3];
		framecount -= temp * lNDFTable[3];
		timecode[4] |= temp;

		temp = framecount / lNDFTable[4];	// seconds
		framecount -= temp * lNDFTable[4];
		timecode[3] = (TCHAR)temp << (4*sizeof(TCHAR));
		temp = framecount / lNDFTable[5];
		framecount -= temp * lNDFTable[5];
		timecode[3] |= temp;

		temp = framecount / lNDFTable[6];	// frames
		framecount -= temp * lNDFTable[6];
		timecode[2] = (TCHAR)temp << (4*sizeof(TCHAR));
		timecode[2] |= framecount;

		// do the checksum
		timecode[10] = 0;
		for (i=0; i<11; i++)
		{
			timecode[10] +=timecode[i];
		}
	}
	else {
		// SVBK timecode format is 8 byte ASCII, Hx10 first
		// hours.  Can't tell whether it's drop or nondrop, so we'll stay 
		// nondrop
		temp = framecount / lNDFTable[0];	// hours
		framecount -= temp * lNDFTable[0];
		timecode[0] = (TCHAR)temp | 0x30;
		temp = framecount / lNDFTable[1];
		framecount -= temp * lNDFTable[1];
		timecode[1] = temp | 0x30;	

		temp = framecount / lNDFTable[2];	// minutes
		framecount -= temp * lNDFTable[2];
		timecode[2] = (TCHAR)temp | 0x30;
		temp = framecount / lNDFTable[3];
		framecount -= temp * lNDFTable[3];
		timecode[3] = temp | 0x30;

		temp = framecount / lNDFTable[4];	// seconds
		framecount -= temp * lNDFTable[4];
		timecode[4] = (TCHAR)temp | 0x30;
		temp = framecount / lNDFTable[5];
		framecount -= temp * lNDFTable[5];
		timecode[5] = temp | 0x30;

		temp = framecount / lNDFTable[6];	// frames
		framecount -= temp * lNDFTable[6];
		timecode[6] = (TCHAR)temp | 0x30;
		timecode[7] = framecount | 0x30;
	}
	return;
}

// eof cdevcom.cpp
