// This is part of Total Recorder Developer Edition (DE).
// Copyright (C) 1998-2001 High Criteria Inc.
// All rights reserved.
//
// RecPlaySave sample application.
//
// This sample loads the Total Recorder object,
// saves Total Recorder's settings,
// records 10 sec. from sound board with phone quality,
// plays the recorded sound, saves it to a file,
// restores settings, and releases Total Recorder object.
//
// RecPlaySave.cpp - main sample program

#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <crtdbg.h>
#include <comdef.h>
#include "..\..\..\TotalRecorder_i.h"
#include "resource.h"
#include "EventProcessing.h"
#include "RecPlaySave.h"

#define SECONDS_TO_RECORD      10
#define WM_MESS_RELEASESERVER  WM_USER + 1

#define SAMPLE_DESCR                                                     \
 _T("The sample loads the Total Recorder object, ")                      \
 _T("saves Total Recorder's settings, ")                                 \
 _T("records 10 sec. from sound board with phone quality, ")             \
 _T("plays the recorded sound, saves it to a file, ")                    \
 _T("restores settings and releases the Total Recorder object.\r\n\r\n") \
 _T("To start sample press the \"Run sample\" button.")

// Function prototypes

BOOL CALLBACK DlgWndProc(
	HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
void LoadServer();
void ReleaseServer();
void StartRecording();
void SaveChanges();
void AddServerErrMess(LPCTSTR tcszMess);

// Global variables

HWND              g_hDlg;                     // Main dialog window handle
int               g_nExitCode           = 0;  // Application return code
CEventProcessing  g_EvProcs;
DWORD             g_dwConnId            = NULL;
VARIANT_BOOL      g_bOldMessMode;
TCHAR             g_tszSysErrDescr[256] = _T("");
TCHAR             g_tszErrMess    [256] = _T("");

enum
{
	StNothing,        // before recording starts
	StRecording,      // after recording starts
	StAfterRecording, // before recording stops and playing starts
	StPlaying,        // after playing started
	StAfterPlaying    // after playing stopped
}                 g_Stage               = StNothing;

IConnectionPoint*                   g_pIConnPnt = NULL;
ITotalRecorderApplication*          g_pTRApp    = NULL;
ITotalRecorderDocument*             g_pTRDoc    = NULL;
ITotalRecorderRecordingParameters*  g_pRCPOld   = NULL;

// Error processing

void CreateErrMess(HRESULT hRes, LPCTSTR tszFunction)
{
	LPTSTR  tszMsgBuf = NULL;

	FormatMessage(
			FORMAT_MESSAGE_ALLOCATE_BUFFER
		| FORMAT_MESSAGE_IGNORE_INSERTS
		| FORMAT_MESSAGE_MAX_WIDTH_MASK
		| FORMAT_MESSAGE_FROM_SYSTEM,    // source and processing options
		NULL,                            // pointer to message source
		hRes,                            // requested message identifier
		LANG_USER_DEFAULT,               // language identifier
		(LPTSTR) &tszMsgBuf,             // pointer to message buffer
		0,                               // maximum size of message buffer
		NULL);                           // addr of array of mess inserts
	if (tszMsgBuf)
	{
		lstrcpyn(g_tszSysErrDescr, tszMsgBuf, SIZEOF_ARRAY(g_tszSysErrDescr));
		LocalFree(tszMsgBuf);
	}
	else
		_tcscpy(g_tszSysErrDescr, _T("?"));

	_sntprintf(g_tszErrMess, SIZEOF_ARRAY(g_tszErrMess),
		_T("Unexpected error %Xx (%s) in [%s]"),
		hRes, g_tszSysErrDescr, tszFunction);
	g_tszErrMess[SIZEOF_ARRAY(g_tszErrMess) - 1] = 0;
}

#define ErrMess(/* LPCTSTR */ Mess) \
	MessageBox(g_hDlg, Mess, _T("RecPlaySave: error"), \
		MB_OK | MB_ICONEXCLAMATION)

#define RunWithErrChk(Function)  if (FAILED(hRes = Function)) \
	CreateErrMess(hRes, _T(#Function))

#define OutIfErr(OutOperator)  if (FAILED(hRes)) \
	{ ErrMess(g_tszErrMess); OutOperator; }
	// OutOperator can be return, break, ...

#define StopAll  { PostMessage(g_hDlg, WM_MESS_RELEASESERVER, 0, 0); return; }

#define RunAndMessIfErr(Func) { RunWithErrChk(Func); OutIfErr((void)0) }
#define RunAndRetIfErr( Func) { RunWithErrChk(Func); OutIfErr(return)  }
#define RunAndStopIfErr(Func) { RunWithErrChk(Func); OutIfErr(StopAll) }

//--------------------------------------------------------------

class CWaitCursor
{
public:
	HCURSOR  m_hCursor;

	CWaitCursor()
	{
		m_hCursor = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
	}

	~CWaitCursor()
	{
		::SetCursor(m_hCursor);
	}
};

//--------------------------------------------------------------

int WINAPI WinMain (HINSTANCE hInstance,
	HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	HRESULT  hRes;

	RunWithErrChk( CoInitialize(NULL) );
	OutIfErr(return 0);

	DialogBox(hInstance,
  	MAKEINTRESOURCE(IDD_DIALOG_TRASAMPLE),
		NULL,
		DlgWndProc);

	CoUninitialize();
  return g_nExitCode;
}

//--------------------------------------------------------------

BOOL CALLBACK DlgWndProc(
	HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	int  wmId;
	int  wmEvent;

	switch (message) 
	{
	case WM_INITDIALOG:
		g_hDlg = hDlg;
		SetDlgItemText(g_hDlg, IDC_EDIT_DESCR, SAMPLE_DESCR); 
		ProcessEvent();
		return TRUE;

	case WM_COMMAND:
		wmId    = LOWORD(wParam);
		wmEvent = HIWORD(wParam);
		switch (wmEvent)
		{
		case BN_CLICKED:
			switch (wmId) 
			{
			case IDCANCEL:
			case IDC_BUTTON_EXIT:
				EndDialog(hDlg, 0);
				return TRUE;
			case IDC_BUTTON_RUN_SAMPLE:
				{
					CWaitCursor  cwc;
					LoadServer();
					if (g_pTRApp)  // successful load server
						StartRecording();
				}
				return TRUE;
			} // switch (wmId)
			return FALSE;
		} // switch (wmEvent)
		return FALSE;

	case WM_QUIT:
		g_nExitCode = wParam;
		return FALSE;

	case WM_DESTROY:
		ReleaseServer();
		return FALSE;

	case WM_MESS_RELEASESERVER:
		ProcessEvent();
		ReleaseServer();
		ProcessEvent();
		return TRUE;

	} // switch (message)
	return FALSE;
}

//--------------------------------------------------------------

void LoadServer()
{
	HRESULT                     hRes;
	ITotalRecorderApplication*  pTRAppl   = NULL;
	IConnectionPointContainer*  picpcTR   = NULL;
	IConnectionPoint*           pIConnPnt = NULL;
	DWORD                       dwConnId  = 0;

	ReleaseServer();

	__try
	{
		RunWithErrChk( CoCreateInstance(
			CLSID_TotalRecorderApplication,
			NULL, CLSCTX_LOCAL_SERVER,
			IID_ITotalRecorderApplication,
			(void**) &pTRAppl) );
		if (hRes == REGDB_E_CLASSNOTREG)
			_tcscpy(g_tszErrMess,
				_T("Total Recorder (Developer Edition) server is not registered."));
		if (hRes == CO_E_SERVER_EXEC_FAILURE
		 && g_tszSysErrDescr[0] != _T('?'))
			_tcscpy(g_tszErrMess, g_tszSysErrDescr);
		OutIfErr(return);

		hRes = g_EvProcs.Init();
		if (FAILED(hRes))
		{
			CreateErrMess(hRes, _T("CreateDispTypeInfo"));
			ErrMess(g_tszErrMess);
			return;
		}

		RunAndRetIfErr( pTRAppl->get_MessageMode(&g_bOldMessMode) );
		RunAndRetIfErr( pTRAppl->QueryInterface(IID_IConnectionPointContainer,
			(void**) &picpcTR) );
		RunAndRetIfErr( picpcTR->FindConnectionPoint(
			DIID__ITotalRecorderApplicationEvents, &pIConnPnt) );
		RunAndRetIfErr( pIConnPnt->Advise(&g_EvProcs, &dwConnId) );

		g_pTRApp    = pTRAppl;
		pTRAppl     = NULL;
		g_pIConnPnt = pIConnPnt;
		pIConnPnt   = NULL;
		g_dwConnId  = dwConnId;
	}
	__finally
	{
		SafeRelease(pTRAppl);
		SafeRelease(picpcTR);
		SafeRelease(pIConnPnt);
	}

	EnableWindow(GetDlgItem(g_hDlg, IDC_BUTTON_RUN_SAMPLE), FALSE);
}

// - - - - - - - - - - - - - - - - - - - -

void ReleaseServer()
{
	HRESULT  hRes;

	if (g_dwConnId)
	{
		_ASSERTE(g_pIConnPnt);
		RunAndMessIfErr( g_pIConnPnt->Unadvise(g_dwConnId) );
		g_dwConnId = 0;
	}

	if (g_pTRApp)
	{
		RunAndMessIfErr( g_pTRApp->put_MessageMode(g_bOldMessMode) );
		if (g_Stage != StNothing)
		{
			RunAndMessIfErr( g_pTRApp->put_Playing  (VARIANT_FALSE) );
			RunAndMessIfErr( g_pTRApp->put_Recording(VARIANT_FALSE) );
		}
		if (g_pRCPOld)
			RunAndMessIfErr( g_pTRApp->put_RecordingParameters(g_pRCPOld) );
	}

	if (g_pTRDoc && g_Stage != StNothing)
	{
		VARIANT  varSave;
		VARIANT  varFileName;

		varSave    .vt      = VT_BOOL;
		varSave    .boolVal = VARIANT_FALSE;
		varFileName.vt      = VT_ERROR;
		varFileName.scode   = DISP_E_PARAMNOTFOUND;
		RunAndMessIfErr( g_pTRDoc->Close(varSave, varFileName) );
	}

	SafeRelease(g_pIConnPnt);
	SafeRelease(g_pTRApp);
	SafeRelease(g_pTRDoc);
	SafeRelease(g_pRCPOld);

	g_Stage = StNothing;
	EnableWindow(GetDlgItem(g_hDlg, IDC_BUTTON_RUN_SAMPLE), TRUE);
}

// - - - - - - - - - - - - - - - - - - - -

void StartRecording()
{
	HRESULT       hRes;
	VARIANT_BOOL  vbRes;
	VARIANT       vParams;

	ITotalRecorderRecordingParameters*  pRCPNew   = NULL;
	ITotalRecorderSoundFormat*          pTRFormat = NULL;

	__try
	{
		// To receive error messages:
		RunAndStopIfErr( g_pTRApp->put_MessageMode(VARIANT_TRUE) );

		RunAndStopIfErr( g_pTRApp->get_AutoJob(&vbRes) );
		if (vbRes)
		{
			ErrMess(_T("Total recorder is busy - it is in AutoJob mode."));
			StopAll;
		}
		RunAndStopIfErr( g_pTRApp->get_Recording(&vbRes) );
		if (vbRes)
		{
			ErrMess(_T("Total recorder is busy - it is recording now."));
			StopAll;
		}
		RunAndStopIfErr( g_pTRApp->get_ModalDialog(&vbRes) );
		if (vbRes)
		{
			ErrMess(_T("Total recorder is busy - modal dialog is active."));
			StopAll;
		}

		RunAndStopIfErr( g_pTRApp->get_ActiveDocument(&g_pTRDoc) );
		RunAndStopIfErr( g_pTRDoc->get_Saved(&vbRes) );
		if (! vbRes)
		{
			ErrMess(_T("Can not start recording - current document is not saved."));
			StopAll;
		}
		vParams.vt    = VT_ERROR;
		vParams.scode = DISP_E_PARAMNOTFOUND;
		RunAndStopIfErr( g_pTRDoc->Close(vParams, vParams) );

		RunAndStopIfErr( g_pTRApp->get_RecordingParameters(&pRCPNew) );
		if (! pRCPNew)  // Total Recorder server is not initializing
			StopAll;
		RunAndStopIfErr( pRCPNew->put_Source(1) );       // SoundBoard
		RunAndStopIfErr( pRCPNew->put_UseMixer(TRUE) );  // Use curr.mixer settings
		RunAndStopIfErr( pRCPNew->get_Format(&pTRFormat) );
		RunAndStopIfErr( pTRFormat->Clear() );
		RunAndStopIfErr( pTRFormat->put_FormatTag(1) );          // PCM
		RunAndStopIfErr( pTRFormat->put_SamplesPerSec(11025) );  // 11025 Hz
		RunAndStopIfErr( pTRFormat->put_BitsPerSample(8) );      // 8 bit
		RunAndStopIfErr( pTRFormat->put_Channels(1) );           // mono
		RunAndStopIfErr( pTRFormat->Validate() );
		RunAndStopIfErr( pTRFormat->get_Valid(&vbRes) );
		if (! vbRes)
		{
			ErrMess(_T("Can not set format to phone quality."));
			StopAll;
		}
		RunAndStopIfErr( pRCPNew ->put_Format(pTRFormat));
		RunAndStopIfErr( g_pTRApp->get_RecordingParameters(&g_pRCPOld) );
		RunAndStopIfErr( g_pTRApp->put_RecordingParameters(pRCPNew) );

		g_Stage = StRecording;
		RunAndStopIfErr( g_pTRApp->put_Recording(TRUE) );
		RunAndStopIfErr( g_pTRApp->get_Recording(&vbRes) );
		if (! vbRes)
		{
			ErrMess(_T("Failed to start recording."));
			StopAll;
		}
	}
	__finally
	{
		SafeRelease(pRCPNew);
		SafeRelease(pTRFormat);
	}
}

// - - - - - - - - - - - - - - - - - - - -

void SaveChanges()
{
	HRESULT       hRes;
	OPENFILENAME  ofn;
	TCHAR         cFileName[MAX_PATH + 1] = _T("TestFile");
	
	memset(&ofn, 0, sizeof(OPENFILENAME));
	ofn.lStructSize = sizeof(OPENFILENAME);
	ofn.hwndOwner   = g_hDlg;
	ofn.lpstrFile   = cFileName;
	ofn.nMaxFile    = MAX_PATH;
	ofn.lpstrFilter = _T("Wave files (*.wav)\0*.wav\0All files (*.*)\0*.*\0");
	ofn.lpstrDefExt = _T("wav"); 
	ofn.Flags       = OFN_HIDEREADONLY | OFN_NOREADONLYRETURN 
		| OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;

	if (GetSaveFileName(&ofn))
	{
		_variant_t  vartFileName;
		VARIANT     vFmt;

		vartFileName = cFileName;
		vFmt.vt      = VT_ERROR;
		vFmt.scode   = DISP_E_PARAMNOTFOUND;
		RunAndStopIfErr( g_pTRDoc->SaveAs(vartFileName, vFmt) );
	}
}

// - - - - - - - - - - - - - - - - - - - -

void ProcessEvent()
{
	_bstr_t  bstrStatus = _T("-");
	TCHAR    tcPos[30]  = _T("-");

	if (g_pTRApp)
	{
		HRESULT       hRes;
		double        dPos;
		BSTR          tmpBStr;
		VARIANT_BOOL  vbRes;

		while (TRUE)
		{
			RunAndStopIfErr( g_pTRApp->get_LastErrorMessage(&tmpBStr) );
			_bstr_t  bstrErr(tmpBStr, FALSE);
			if (bstrErr.length() == 0)
				break;
			AddServerErrMess(bstrErr);
		}

		RunAndStopIfErr( g_pTRApp->get_Position (&dPos) );
		_stprintf(tcPos, _T("%G"), dPos);
		RunAndStopIfErr( g_pTRApp->get_StatusMessage(0, &tmpBStr) );
		_bstr_t  bstrStat(tmpBStr, FALSE);
		bstrStatus = bstrStat;

		switch (g_Stage)
		{
		case StRecording:
			RunAndStopIfErr( g_pTRApp->get_Recording(&vbRes) );
			if (! vbRes)  // was error
				StopAll;
			if (dPos >= SECONDS_TO_RECORD)
			{
				g_Stage = StAfterRecording;  // will be recursive calls
				RunAndStopIfErr( g_pTRApp->put_Recording(FALSE) );
				RunAndStopIfErr( g_pTRApp->put_Position(0) );
				RunAndStopIfErr( g_pTRApp->put_Playing(TRUE) );
				RunAndStopIfErr( g_pTRApp->get_Playing(&vbRes) );
				if (! vbRes)
				{
					ErrMess(_T("Failed to start playing."));
					StopAll;
				}
				g_Stage = StPlaying;
			}
			break;

		case StPlaying:
			RunAndStopIfErr( g_pTRApp->get_Playing(&vbRes) );
			if (! vbRes)  // end of playing
			{
				g_Stage = StAfterPlaying;  // will be recursive calls
				SaveChanges();
				StopAll;
			}
			break;
		}
	}

	SetDlgItemText(g_hDlg, IDC_EDIT_STATUS,   bstrStatus);
	SetDlgItemText(g_hDlg, IDC_EDIT_CURR_POS, tcPos);
}

// - - - - - - - - - - - - - - - - - - - -

void AddServerErrMess(LPCTSTR tcszMess)
{
	SendDlgItemMessage(g_hDlg, IDC_EDIT_ERR_MESS, EM_SETSEL, 
		(WPARAM) -1, (LPARAM) -1);
	SendDlgItemMessage(g_hDlg, IDC_EDIT_ERR_MESS, EM_REPLACESEL, 
		(WPARAM) FALSE, (LPARAM) tcszMess);

	SendDlgItemMessage(g_hDlg, IDC_EDIT_ERR_MESS, EM_SETSEL, 
		(WPARAM) -1, (LPARAM) -1);
	SendDlgItemMessage(g_hDlg, IDC_EDIT_ERR_MESS, EM_REPLACESEL, 
		(WPARAM) FALSE, (LPARAM) _T("\r\n"));
}
