////////////////////////////////////////////////////////////////////// // PromptDb.cpp : Implementation of CPromptDb // // Created by JOEM 03-2000 // Copyright (C) 2000 Microsoft Corporation // All Rights Reserved // /////////////////////////////////////////////////////// JOEM 3-2000 // #include "stdafx.h" #include "PromptDb.h" #include "LocalTTSEngineSite.h" #include "vapiIO.h" #include //#define ENTRY_TAG L"DB_ENTRY" const WCHAR ENTRY_TAG[] = L"DB_ENTRY"; const WCHAR ENTRY_START[] = L"{"; const WCHAR ENTRY_END[] = L"}"; const WCHAR ID_TAG[] = L"ID:"; const WCHAR TEXT_TAG[] = L"TEXT:"; const WCHAR ORIGINAL_TAG[] = L"ORIGINAL:"; const WCHAR TAG_TAG[] = L"TAG:"; const WCHAR FILE_TAG[] = L"FILE:"; const WCHAR FROM_TAG[] = L"FROM:"; const WCHAR TO_TAG[] = L"TO:"; const WCHAR START_PHONE_TAG[] = L"START_PHONE:"; const WCHAR END_PHONE_TAG[] = L"END_PHONE:"; const WCHAR RIGHT_CONTEXT_TAG[] = L"RIGHT_CONTEXT:"; const WCHAR LEFT_CONTEXT_TAG[] = L"LEFT_CONTEXT:"; const int g_iBase = 16; ////////////////////////////////////////////////////////////////////// // Db // // Construction/Destruction /////////////////////////////////////////////////////// JOEM 3-2000 // CDb::CDb() { pszPathName = NULL; pszTempName = NULL; pszLogicalName = NULL; } CDb::~CDb() { if ( pszPathName ) { free ( pszPathName ); pszPathName = NULL; } if ( pszTempName ) { free ( pszTempName ); pszTempName = NULL; } if ( pszLogicalName ) { free ( pszLogicalName ); pszLogicalName = NULL; } } ////////////////////////////////////////////////////////////////////// // CPromptDb // // Construction/Destruction /////////////////////////////////////////////////////// JOEM 3-2000 // HRESULT CPromptDb::FinalConstruct() { SPDBG_FUNC( "CPromptDb::FinalConstruct" ); HRESULT hr = S_OK; m_pActiveDb = NULL; m_unIndex = 0; m_vcRef = 1; m_pOutputFormatId = NULL; m_pOutputFormat = NULL; m_flEntryGain = 1.0; // This will be set in registry. m_flXMLVolume = 1.0; // Full volume unless XML changes it. m_flXMLRateAdj = 1.0; // Regular rate unless XML changes it. if ( SUCCEEDED( hr ) ) { m_pVapiIO = VapiIO::ClassFactory(); if ( !m_pVapiIO ) { hr = E_OUTOFMEMORY; } } // create the rate changer if ( SUCCEEDED(hr) ) { m_pTsm = new CTsm( g_dRateScale[BASE_RATE] ); if ( !m_pTsm ) { hr = E_OUTOFMEMORY; } } SPDBG_REPORT_ON_FAIL( hr ); return hr; } void CPromptDb::FinalRelease() { USHORT i = 0; m_pActiveDb = NULL; for ( i=0; i < m_apDbList.GetSize(); i++ ) { if ( m_apDbList[i] ) { delete m_apDbList[i]; m_apDbList[i] = NULL; } } m_apDbList.RemoveAll(); for ( i=0; iAddRef(); return S_OK; } ////////////////////////////////////////////////////////////////////// // CPromptDb::AddRef // /////////////////////////////////////////////////////// JOEM 4-2000 // ULONG CPromptDb::AddRef() { return InterlockedIncrement(&m_vcRef); } ////////////////////////////////////////////////////////////////////// // CPromptDb::Release // /////////////////////////////////////////////////////// JOEM 4-2000 // ULONG CPromptDb::Release() { if ( 0 == InterlockedDecrement(&m_vcRef) ) { delete this; return 0; } return (ULONG) m_vcRef; } // // IPromptDb INTERFACE FUNCTION IMPLEMENTATIONS // ////////////////////////////////////////////////////////////////////// // CPromptDb::NewDb // // Creates a new Db. // /////////////////////////////////////////////////////// JOEM 3-2000 // STDMETHODIMP CPromptDb::NewDb(const WCHAR* logicalName, const WCHAR* pathName) { SPDBG_FUNC( "CPromptDb::NewDb" ); HRESULT hr = S_OK; CDb* db = NULL; // See if this Db already exists. hr = ActivateDbName(logicalName); if ( SUCCEEDED(hr) ) { return E_INVALIDARG; } db = new CDb; if ( db ) { hr = S_OK; } if ( SUCCEEDED(hr) ) { db->pszLogicalName = _wcsdup(logicalName); if ( !db->pszLogicalName ) { hr = E_OUTOFMEMORY; } } if ( SUCCEEDED(hr) ) { db->pszPathName = _wcsdup(pathName); if ( !db->pszPathName ) { hr = E_OUTOFMEMORY; } } if ( SUCCEEDED(hr) ) { m_apDbList.Add(db); } if ( FAILED(hr) ) { if ( db ) { delete db; } } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::AddDb // // Adds a Db to the list, and activates it if desired. // /////////////////////////////////////////////////////// JOEM 3-2000 // STDMETHODIMP CPromptDb::AddDb(const WCHAR* logicalName, const WCHAR* pathName, const WCHAR* pszIdSet, const USHORT loadOption) { SPDBG_FUNC( "CPromptDb::AddDb" ); HRESULT hr = S_OK; CDb* pActiveDb = m_pActiveDb; if ( !pathName || !wcslen(pathName) ) { hr = E_INVALIDARG; } if ( SUCCEEDED(hr) ) { if ( !FileExist( pathName ) ) { hr = PEERR_DB_NOT_FOUND; } } if ( SUCCEEDED(hr) ) { // if there is logicalName specified, activate or create it. if ( logicalName ) { hr = ActivateDbName(logicalName); if ( FAILED(hr) ) { hr = NewDb(logicalName, pathName); if ( SUCCEEDED(hr) ) { hr = ActivateDbName(logicalName); } } } else { // if no current Db, activate the default if ( !m_pActiveDb ) { hr = ActivateDbName(L"DEFAULT"); } } } // Set the Active Db's new path name if ( SUCCEEDED(hr) ) { if ( m_pActiveDb->pszPathName ) { free(m_pActiveDb->pszPathName); m_pActiveDb->pszPathName = wcsdup(pathName); if ( !m_pActiveDb->pszPathName ) { hr = E_OUTOFMEMORY; } } } // Load it? if ( SUCCEEDED(hr) ) { if ( loadOption == DB_LOAD ) { if ( SUCCEEDED( hr ) ) { hr = LoadDb(pszIdSet); } } } // Reset the active Db m_pActiveDb = pActiveDb; SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::LoadDb // // Initializes the hash tables for a new Db. // /////////////////////////////////////////////////////// JOEM 3-2000 // STDMETHODIMP CPromptDb::LoadDb(const WCHAR* pszIdSet) { SPDBG_FUNC( "CPromptDb::LoadDb" ); HRESULT hr = S_OK; FILE* fp = NULL; SPDBG_ASSERT( m_pActiveDb ); SPDBG_ASSERT( m_pActiveDb->pszPathName ); fp = _wfopen (m_pActiveDb->pszPathName, L"r"); if ( !fp ) { hr = E_ACCESSDENIED; } if ( SUCCEEDED(hr) ) { hr = LoadIdHash(fp, pszIdSet); fclose (fp); } if ( SUCCEEDED(hr) ) { hr = IndexTextHash(); } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::UnloadDb // // Deactivates and unloads a Db. Makes the default Db active, or the // first available Db if the default doesn't exist. // /////////////////////////////////////////////////////// JOEM 3-2000 // STDMETHODIMP CPromptDb::UnloadDb(const WCHAR* pszLogicalName) { SPDBG_FUNC( "CPromptDb::UnloadDb" ); HRESULT hr = S_OK; USHORT i = 0; USHORT dbIndex = 0; SPDBG_ASSERT(pszLogicalName); for ( i=0; ipszLogicalName) == 0 ) { if ( wcscmp(pszLogicalName, m_pActiveDb->pszLogicalName) == 0 ) { m_pActiveDb = NULL; } delete m_apDbList[i]; m_apDbList[i] = NULL; m_apDbList.RemoveAt( i ); } } if ( !m_pActiveDb ) { hr = ActivateDbName(L"DEFAULT"); if ( FAILED(hr) ) { hr = ActivateDbNumber(0); } if( FAILED(hr) ) { hr = S_FALSE; } } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::ActivateDbName // // Activates the Db specified by logical name. // /////////////////////////////////////////////////////// JOEM 3-2000 // STDMETHODIMP CPromptDb::ActivateDbName(const WCHAR* pszLogicalName) { SPDBG_FUNC( "CPromptDb::ActivateDbName" ); HRESULT hr = PEERR_DB_NOT_FOUND; WCHAR* pszDb = NULL; USHORT unSize = 0; USHORT i = 0; SPDBG_ASSERT(pszLogicalName); if ( m_pActiveDb ) { // See if the Db is already the active one if ( !wcscmp(m_pActiveDb->pszLogicalName, pszLogicalName ) ) { hr = S_OK; } } // Did the current Db match? if ( FAILED(hr) ) { unSize = (USHORT) m_apDbList.GetSize(); for (i = 0; i < unSize; i++) { if ( _wcsicmp(pszLogicalName, m_apDbList[i]->pszLogicalName) == 0 ) { m_pActiveDb = m_apDbList[i]; hr = S_OK; } } } // Don't report failures here - this func is allowed to fail frequently. //SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::ActivateDbNumber // // Activates the Db specified by number. // /////////////////////////////////////////////////////// JOEM 3-2000 // STDMETHODIMP CPromptDb::ActivateDbNumber(const USHORT unIndex) { SPDBG_FUNC( "CPromptDb::ActivateDbNumber" ); HRESULT hr = S_OK; USHORT unSize = 0; USHORT i = 0; unSize = (USHORT) m_apDbList.GetSize(); if ( unIndex >= unSize ) { hr = PEERR_DB_NOT_FOUND; } if ( SUCCEEDED(hr) ) { m_pActiveDb = m_apDbList[unIndex]; if ( !m_pActiveDb ) { hr = E_UNEXPECTED; } } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::UpdateDb // // When creating a Db file, this updates the real Db file with the // contents of the temp file. // /////////////////////////////////////////////////////// JOEM 3-2000 // STDMETHODIMP CPromptDb::UpdateDb(WCHAR* pszPath) { SPDBG_FUNC( "CPromptDb::UpdateDb" ); HRESULT hr = S_OK; FILE* fp = NULL; WCHAR* id = NULL; CPromptEntry* value = NULL; USHORT idx1 = 0; USHORT idx2 = 0; WCHAR backupName[_MAX_PATH + 1]; SPDBG_ASSERT( m_pActiveDb ); // New name? if ( pszPath ) { if ( m_pActiveDb->pszPathName ) { free( m_pActiveDb->pszPathName); } m_pActiveDb->pszPathName = wcsdup(pszPath); if ( !m_pActiveDb->pszPathName ) { hr = E_OUTOFMEMORY; } } // Create the temp file name if ( SUCCEEDED(hr) ) { hr = TempName(); } // Write the entries to the temp file if ( SUCCEEDED(hr) ) { if ((fp = _wfopen(m_pActiveDb->pszTempName, L"w")) == NULL) { hr = E_ACCESSDENIED; } if ( SUCCEEDED(hr) ) { fwprintf (fp, L"#\n# PROMPT Database, generated automatically\n#\n\n"); while ( SUCCEEDED(hr) ) { hr = m_pActiveDb->idHash.NextKey(&idx1, &idx2, &id); if ( id == NULL ) { break; } if ( SUCCEEDED(hr) ) { value = (CPromptEntry *) m_pActiveDb->idHash.Find(id); if ( !value ) { hr = E_FAIL; } } if ( SUCCEEDED(hr) ) { hr = WriteEntry (fp, value); } } fclose (fp); } } // Now backup the old Db file if ( SUCCEEDED(hr) ) { // Make a backupName wcscat( wcsncpy( backupName, m_pActiveDb->pszPathName, _MAX_PATH-1 ), L"~" ); // This "remove" is necessary, since the "rename" // would fail if the file exists. if ( FileExist( backupName ) ) { _wremove (backupName); } if ( FileExist( m_pActiveDb->pszPathName) ) { if ( _wrename( m_pActiveDb->pszPathName, backupName ) !=0 ) { hr = E_ACCESSDENIED; } } } if ( SUCCEEDED(hr) ) { if ( _wrename( m_pActiveDb->pszTempName, m_pActiveDb->pszPathName ) !=0 ) { hr = E_ACCESSDENIED; } } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb // // CountDb // /////////////////////////////////////////////////////// JOEM 3-2000 // STDMETHODIMP CPromptDb::CountDb(USHORT *unCount) { SPDBG_FUNC( "CPromptDb::CountDb" ); *unCount = (USHORT) m_apDbList.GetSize(); return S_OK; } ////////////////////////////////////////////////////////////////////// // CPromptDb::LoadIdHash // // Makes a hash table for entry ID numbers, using unique key values. // Adds all the Db entry ids to the table. // /////////////////////////////////////////////////////// JOEM 3-2000 // HRESULT CPromptDb::LoadIdHash(FILE *fp, const WCHAR* pszIdSet) { SPDBG_FUNC( "CPromptDb::LoadIdHash" ); HRESULT hr = S_OK; CPromptEntry* entry = NULL; const WCHAR* entryInfo = NULL; SPDBG_ASSERT (fp); // S_FALSE indicates no more entries. while ( hr == S_OK ) { hr = ReadEntry (fp, &entry); if ( hr == S_OK && entry ) { if ( SUCCEEDED(hr) ) { hr = entry->GetId(&entryInfo); // prepend the IDSET info to the id if ( pszIdSet && wcslen(pszIdSet) ) { WCHAR* pszFullId = NULL; int iLen = wcslen(pszIdSet) + wcslen(entryInfo) + 1; pszFullId = new WCHAR[iLen]; if ( pszFullId ) { wcscpy(pszFullId, pszIdSet); wcscat(pszFullId, entryInfo); hr = entry->SetId(pszFullId); if ( SUCCEEDED(hr) ) { hr = entry->GetId(&entryInfo); } delete [] pszFullId; } else { hr = E_OUTOFMEMORY; } } if ( SUCCEEDED(hr) ) { hr = m_pActiveDb->idHash.BuildEntry( entryInfo, entry ); if ( hr == E_INVALIDARG ) { hr = PEERR_DB_DUPLICATE_ID; } entryInfo = NULL; } } entry->Release(); } } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::SearchDb // // Searches the text in the Db entries for items that can be // concatenated to match the query text. If such a list exists, // it is put in m_aSearchList. // /////////////////////////////////////////////////////// JOEM 3-2000 // STDMETHODIMP CPromptDb::SearchDb(const WCHAR* pszQuery, USHORT* unIdCount) { SPDBG_FUNC( "CPromptDb::SearchDb" ); HRESULT hr = S_OK; const CDynStrArray* list = NULL; USHORT i = 0; IPromptEntry* pIPE = NULL; // make sure the search list is clear for ( i=0; itextHash.Find(pszQuery); } if ( !list ) { hr = E_FAIL; } else { // If searching the text hash resulted in a list of Ids, save the list. for ( i=0; im_aDstr.GetSize(); i++ ) { m_aSearchList.Add( list->m_aDstr[i].dstr ); } } if ( SUCCEEDED(hr) ) { // go through the list of IDs for this text // Make sure each id in the list exists; if not, remove it from list. for ( i=0; iRelease(); } else { m_aSearchList[i].dstr.Clear(); m_aSearchList.RemoveAt( i ); } } } } if ( m_aSearchList.GetSize() ) { *unIdCount = (USHORT) m_aSearchList.GetSize(); hr = S_OK; } // Don't report failures here - this func is allowed to fail frequently. //SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::RetrieveSearchItem // // /////////////////////////////////////////////////////// JOEM 5-2000 // STDMETHODIMP CPromptDb::RetrieveSearchItem(const USHORT unId, const WCHAR** ppszId) { SPDBG_FUNC( "CPromptDb::RetrieveSearchItem" ); HRESULT hr = S_OK; if ( unId >= m_aSearchList.GetSize() ) { hr = E_INVALIDARG; } if ( SUCCEEDED(hr) ) { *ppszId = m_aSearchList[unId].dstr; } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::GetLogicalName // // /////////////////////////////////////////////////////// JOEM 3-2000 // STDMETHODIMP CPromptDb::GetLogicalName(const WCHAR** ppszLogicalName) { SPDBG_FUNC( "CPromptDb::GetLogicalName" ); HRESULT hr = S_OK; SPDBG_ASSERT( m_pActiveDb ); *ppszLogicalName = m_pActiveDb->pszLogicalName; SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::FindEntry // // Locates a Db entry in the hash, and gets an interface for it. // /////////////////////////////////////////////////////// JOEM 3-2000 // STDMETHODIMP CPromptDb::FindEntry(const WCHAR *id, IPromptEntry** ppIPE) { SPDBG_FUNC( "CPromptDb::FindEntry" ); HRESULT hr = S_OK; CPromptEntry* entry = NULL; const WCHAR* entryFileName = NULL; SPDBG_ASSERT(m_pActiveDb); SPDBG_ASSERT(id); entry = (CPromptEntry*) m_pActiveDb->idHash.Find(id); if ( !entry ) { hr = PEERR_DB_ID_NOT_FOUND; } if ( SUCCEEDED(hr) ) { hr = entry->GetFileName(&entryFileName); if ( SUCCEEDED(hr) ) { if ( FileExist( entryFileName ) ) { hr = entry->QueryInterface(IID_IPromptEntry, (void**)ppIPE); } else { *ppIPE = NULL; hr = E_ACCESSDENIED; } entryFileName = NULL; } } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::NewEntry // /////////////////////////////////////////////////////// JOEM 5-2000 // STDMETHODIMP CPromptDb::NewEntry(IPromptEntry **ppIPE) { SPDBG_FUNC( "CPromptDb::NewEntry" ); CPromptEntry* newEntry = new CPromptEntry; HRESULT hr = S_OK; if ( newEntry ) { *ppIPE = newEntry; } else { hr = E_OUTOFMEMORY; } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::SaveEntry // // Adds a Db entry to the Db's hash table // /////////////////////////////////////////////////////// JOEM 3-2000 // STDMETHODIMP CPromptDb::SaveEntry(IPromptEntry* pIPE) { SPDBG_FUNC( "CPromptDb::SaveEntry" ); HRESULT hr = S_OK; const WCHAR* entryId = NULL; double to = 0.0; double from = 0.0; if ( SUCCEEDED(hr) ) { hr = pIPE->GetStart(&from); } if ( SUCCEEDED(hr) ) { hr = pIPE->GetEnd(&to); } if ( SUCCEEDED(hr) ) { USHORT validTime = (from==0.0 && to == -1.0) || (from>=0.0 && to>from); const WCHAR* entryFile = NULL; const WCHAR* entryText = NULL; pIPE->GetId(&entryId); pIPE->GetText(&entryText); pIPE->GetFileName(&entryFile); if ( !entryId || !entryText || !entryFile || !validTime ) { hr = E_INVALIDARG; } } if ( SUCCEEDED(hr) ) { hr = m_pActiveDb->idHash.BuildEntry( entryId, pIPE ); } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::RemoveEntry // // /////////////////////////////////////////////////////// JOEM 3-2000 // STDMETHODIMP CPromptDb::RemoveEntry(const WCHAR *id) { SPDBG_FUNC( "CPromptDb::RemoveEntry" ); HRESULT hr = S_OK; CPromptEntry* pEntry = NULL; SPDBG_ASSERT(m_pActiveDb); SPDBG_ASSERT(id); if (SUCCEEDED(hr)) { // CHash will Release it hr = m_pActiveDb->idHash.DeleteEntry(id); } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::OpenEntryFile // // Opens the specified entry. // /////////////////////////////////////////////////////// JOEM 5-2000 // STDMETHODIMP CPromptDb::OpenEntryFile(IPromptEntry *pIPE, WAVEFORMATEX* pWaveFormatEx) { SPDBG_FUNC( "CPromptDb::OpenEntryFile" ); HRESULT hr = S_OK; const WCHAR* entryFile = NULL; SPDBG_ASSERT (pIPE); if ( SUCCEEDED(hr) ) { hr = pIPE->GetFileName(&entryFile); } if ( SUCCEEDED(hr) ) { if ( m_pVapiIO->OpenFile(entryFile, VAPI_IO_READ) != 0 ) { hr = E_ACCESSDENIED; } entryFile = NULL; } if ( SUCCEEDED(hr) ) { if ( m_pVapiIO->Format(NULL, NULL, pWaveFormatEx) != 0 ) { hr = E_FAIL; } } if ( FAILED(hr) ) { m_pVapiIO->CloseFile(); } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::CloseEntryFile // // Retrieves the specified audio samples from the database entry. // /////////////////////////////////////////////////////// JOEM 5-2000 // STDMETHODIMP CPromptDb::CloseEntryFile() { m_pVapiIO->CloseFile(); return S_OK; } ////////////////////////////////////////////////////////////////////// // CPromptDb::GetNextEntry // // Retrieves the entry, based on the unique key for the Id Hash. // To retrieve the first entry, use punId1=0, punId2=0. The Id's // will be updated with the values that can be used to retrieve the // next entry. // /////////////////////////////////////////////////////// JOEM 6-2000 // STDMETHODIMP CPromptDb::GetNextEntry(USHORT* punId1, USHORT* punId2, IPromptEntry** ppIPE) { SPDBG_FUNC( "CPromptDb::GetNextEntry" ); HRESULT hr = S_OK; WCHAR* pszNextKey = NULL; SPDBG_ASSERT(punId1); SPDBG_ASSERT(punId2); hr = m_pActiveDb->idHash.NextKey( punId1, punId2, &pszNextKey ); if ( !pszNextKey ) { hr = E_FAIL; } if ( SUCCEEDED(hr) ) { hr = FindEntry(pszNextKey, ppIPE); } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::SetOutputFormat // // Sets the format in which samples are returned to the calling app. // /////////////////////////////////////////////////////// JOEM 5-2000 // STDMETHODIMP CPromptDb::SetOutputFormat(const GUID *pOutputFormatId, const WAVEFORMATEX *pOutputFormat) { SPDBG_FUNC( "CPromptDb::SetOutputFormat" ); HRESULT hr = S_OK; if ( pOutputFormatId && *pOutputFormatId == SPDFID_Text ) { m_pOutputFormatId = pOutputFormatId; if ( m_pOutputFormat ) { delete m_pOutputFormat; m_pOutputFormat = NULL; } } else if ( pOutputFormat ) { // Do we need to change the format? if ( !m_pOutputFormat || m_pOutputFormat->wFormatTag != pOutputFormat->wFormatTag || m_pOutputFormat->nChannels != pOutputFormat->nChannels || m_pOutputFormat->nSamplesPerSec != pOutputFormat->nSamplesPerSec || m_pOutputFormat->nAvgBytesPerSec != pOutputFormat->nAvgBytesPerSec || m_pOutputFormat->nBlockAlign != pOutputFormat->nBlockAlign || m_pOutputFormat->wBitsPerSample != pOutputFormat->wBitsPerSample || m_pOutputFormat->cbSize != pOutputFormat->cbSize ) { // free the current waveformatex if ( m_pOutputFormat ) { free(m_pOutputFormat); m_pOutputFormat = NULL; } // this needs to copy the output format, not just point to it, // because engine will pass in const pointer. m_pOutputFormat = (WAVEFORMATEX*) malloc( sizeof(WAVEFORMATEX) + pOutputFormat->cbSize ); if ( !m_pOutputFormat ) { hr = E_OUTOFMEMORY; } else { m_pOutputFormatId = pOutputFormatId; memcpy(m_pOutputFormat, pOutputFormat, sizeof(WAVEFORMATEX) + pOutputFormat->cbSize ); } if ( SUCCEEDED(hr) ) { m_converter.SetOutputFormat(m_pOutputFormat); } if ( SUCCEEDED(hr) && m_pTsm ) { m_pTsm->SetSamplingFrequency(m_pOutputFormat->nSamplesPerSec); } } } else { hr = E_INVALIDARG; } return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::SetEntryGain // // Sets the gain factor (from registry) for Prompt entries. // /////////////////////////////////////////////////////// JOEM 6-2000 // STDMETHODIMP CPromptDb::SetEntryGain(const double dEntryGain) { SPDBG_FUNC( "CPromptDb::SetEntryGain" ); m_flEntryGain = (float)dEntryGain; return S_OK; } ////////////////////////////////////////////////////////////////////// // CPromptDb::SetXMLVolume // // Adjust the volume from XML tag. // /////////////////////////////////////////////////////// JOEM 6-2000 // STDMETHODIMP CPromptDb::SetXMLVolume(const ULONG ulXMLVol) { SPDBG_FUNC( "CPromptDb::SetXMLVolume" ); SPDBG_ASSERT(ulXMLVol <=100); m_flXMLVolume = ((double) ulXMLVol) / 100; return S_OK; } ////////////////////////////////////////////////////////////////////// // CPromptDb::SetXMLRate // // Adjust the rate from XML tag. // /////////////////////////////////////////////////////// JOEM 11-2000 // STDMETHODIMP CPromptDb::SetXMLRate(const long lXMLRate) { SPDBG_FUNC( "CPromptDb::SetXMLRate" ); ComputeRateAdj(lXMLRate, &m_flXMLRateAdj); return S_OK; } ////////////////////////////////////////////////////////////////////// // CPromptDb::SendEntrySamples // // Retrieves the specified audio samples from the database entry, // applies rate change, converts them, applies gain/vol, and sends // them to output site. // /////////////////////////////////////////////////////// JOEM 5-2000 // STDMETHODIMP CPromptDb::SendEntrySamples(IPromptEntry* pIPE, ISpTTSEngineSite* pOutputSite, ULONG ulTextOffset, ULONG ulTextLen) { HRESULT hr = S_OK; double entryTo = 0.0; double entryFrom = 0.0; ULONG pcbWritten = 0; void* pvSamples = NULL; void* pvNewSamples = NULL; int iNumSamples = 0; int iNumNewSamples = 0; USHORT unVol = 1; // Full vol unless app changes it double dVol = 1.0; // Full vol unless app changes it long lRateAdj = 1; // Regular rate unless app changes it float flRateAdj = 1.0; // Regular rate unless app changes it double dBuff = 0.0; USHORT i = 0; bool fConvert = false; WAVEFORMATEX wavFormat; SPDBG_ASSERT(pIPE); SPDBG_ASSERT(pOutputSite); hr = OpenEntryFile(pIPE, &wavFormat); if ( SUCCEEDED(hr) ) { // Does the current prompt format need converting? if ( m_pOutputFormat ) { if (wavFormat.wFormatTag != m_pOutputFormat->wFormatTag || wavFormat.nAvgBytesPerSec != m_pOutputFormat->nAvgBytesPerSec || wavFormat.nBlockAlign != m_pOutputFormat->nBlockAlign || wavFormat.nChannels != m_pOutputFormat->nChannels || wavFormat.nSamplesPerSec != m_pOutputFormat->nSamplesPerSec || wavFormat.wBitsPerSample != m_pOutputFormat->wBitsPerSample ) { fConvert = true; m_converter.SetInputFormat(&wavFormat); } } // get the start and end points, and clean them up if necessary. if ( SUCCEEDED(hr) ) { hr = pIPE->GetStart(&entryFrom); if ( SUCCEEDED(hr) ) { if ( entryFrom < 0.0 ) { entryFrom = 0.0; } hr = pIPE->GetEnd(&entryTo); } if ( SUCCEEDED(hr) ) { long lDataSize = 0; double dEnd = 0.0; // make sure we're not trying to read past end of file m_pVapiIO->GetDataSize(&lDataSize); dEnd = ((double)lDataSize)/wavFormat.nBlockAlign/wavFormat.nSamplesPerSec; if ( entryTo > dEnd ) { entryTo = dEnd; } if ( entryTo != -1.0 && entryFrom >= entryTo ) { entryFrom = entryTo; } } } if ( SUCCEEDED(hr) ) { // read the samples if ( SUCCEEDED(hr) ) { if ( m_pVapiIO->ReadSamples( entryFrom, entryTo, &pvSamples, &iNumSamples, 1) != 0 ) { hr = E_FAIL; } if ( iNumSamples == 0 ) { hr = E_FAIL; } } // convert to desired output format if ( SUCCEEDED(hr) && fConvert && pvSamples ) { hr = m_converter.ConvertSamples(pvSamples, iNumSamples, (void**) &pvNewSamples, &iNumNewSamples); if ( SUCCEEDED(hr) ) { delete [] pvSamples; pvSamples = pvNewSamples; iNumSamples = iNumNewSamples; pvNewSamples = NULL; iNumNewSamples = 0; } } // do rate change. Must be format-converted already! if ( SUCCEEDED(hr) && m_pTsm ) { // Don't GetActions - the rate flag is kept on since TTS eng needs it too. hr = pOutputSite->GetRate( &lRateAdj ); if ( SUCCEEDED(hr) ) { ComputeRateAdj(lRateAdj, &flRateAdj); flRateAdj *= m_flXMLRateAdj; m_pTsm->SetScaleFactor( (double) flRateAdj ); if ( flRateAdj != 1.0 && pvSamples ) { // can't adjust rate for chunks smaller than the frame length if ( iNumSamples > m_pTsm->GetFrameLen() ) { hr = m_pTsm->AdjustTimeScale( pvSamples, iNumSamples, &pvNewSamples, &iNumNewSamples, m_pOutputFormat ); if ( SUCCEEDED(hr) ) { delete [] pvSamples; pvSamples = pvNewSamples; iNumSamples = iNumNewSamples; pvNewSamples = NULL; iNumNewSamples = 0; } } } } } // check for volume change if ( SUCCEEDED(hr) ) { // Prompt Eng doesn't GetActions - the Vol flag is kept on since TTS eng needs it. hr = pOutputSite->GetVolume( &unVol ); } // Multiply the three volume sources if ( SUCCEEDED(hr) ) { dVol = ((double) unVol) / 100; // make dVol fractional dVol *= m_flEntryGain; // gain from registry dVol *= m_flXMLVolume; // gain from XML if ( dVol != 1.0 ) { hr = ApplyGain( pvSamples, &pvNewSamples, iNumSamples, dVol ); // Did ApplyGain need to create a new buffer? if ( pvNewSamples ) { // send the volume modified buff instead of the original delete [] pvSamples; pvSamples = pvNewSamples; pvNewSamples = NULL; } } } // EVENTS: // Send a private engine event at beginning of each prompt if ( SUCCEEDED(hr) ) { hr = SendEvent(SPEI_TTS_PRIVATE, pOutputSite, 0, ulTextOffset, ulTextLen); } // Mid-prompt pausing requires word boundaries. We don't know word-boundaries within // prompts, so approximate them by sending a word boundary event for every half-second // of audio. if ( SUCCEEDED(hr) ) { ULONG ulOffset = 0; int iHalfSecs = iNumSamples / m_pOutputFormat->nSamplesPerSec * 2; for ( i = 0; i <= iHalfSecs; i++ ) { ulOffset = i * (m_pOutputFormat->nSamplesPerSec / 2) * m_pOutputFormat->nBlockAlign; SendEvent(SPEI_WORD_BOUNDARY, pOutputSite, ulOffset, ulTextOffset, ulTextLen); } } // write samples to output site if ( SUCCEEDED(hr) ) { hr = ((CLocalTTSEngineSite*)(pOutputSite))->Write(pvSamples, iNumSamples * m_pOutputFormat->nBlockAlign, &pcbWritten); if ( SUCCEEDED(hr) ) { ((CLocalTTSEngineSite*)(pOutputSite))->UpdateBytesWritten(); } delete [] pvSamples; pvSamples = NULL; iNumSamples = 0; pcbWritten = 0; } } // if ( SUCCEEDED(hr) ) hr = CloseEntryFile(); } // if ( SUCCEEDED(hr) if ( pvSamples ) { delete[]pvSamples; pvSamples = NULL; iNumSamples = 0; } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // // All of the following functions are non-interface helpers. // ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// // CPromptDb::ComputeRateAdj // // Computes the rate multiplier. // /////////////////////////////////////////////////////// JOEM 11-2000 // void CPromptDb::ComputeRateAdj(const long lRate, float* flRate) { SPDBG_FUNC( "CPromptDb::ComputeRateAdj" ); SPDBG_ASSERT(flRate); if ( lRate < 0 ) { if ( lRate < MIN_RATE ) { *flRate = 1.0 / g_dRateScale[0 - MIN_RATE]; } else { *flRate = 1.0 / g_dRateScale[0 - lRate]; } } else { if ( lRate > MAX_RATE ) { *flRate = g_dRateScale[MAX_RATE]; } else { *flRate = g_dRateScale[lRate]; } } } ////////////////////////////////////////////////////////////////////// // CPromptDb::SendEvent // // Sends the specified engine event to SAPI. // ////////////////////////////////////////////////////// JOEM 11-2000 // STDMETHODIMP CPromptDb::SendEvent(const SPEVENTENUM eventName, ISpTTSEngineSite* pOutputSite, const ULONG ulAudioOffset, const ULONG ulTextOffset, const ULONG ulTextLen) { SPDBG_ASSERT(pOutputSite); SPEVENT event; event.eEventId = eventName; event.elParamType = SPET_LPARAM_IS_UNDEFINED; event.ulStreamNum = 0; event.ullAudioStreamOffset = ulAudioOffset; //LocalEngineSite will add prev. bytes event.lParam = ulTextOffset; event.wParam = ulTextLen; return pOutputSite->AddEvents( &event, 1 ); } ////////////////////////////////////////////////////////////////////// // CPromptDb::ApplyGain // // Volume adjustment. // ////////////////////////////////////////////////////// JOEM 01-2001 // STDMETHODIMP CPromptDb::ApplyGain(const void* pvInBuff, void** ppvOutBuff, const int iNumSamples, double dGain) { SPDBG_FUNC( "CPromptDb::ApplyGain" ); HRESULT hr = S_OK; int i = 0; SPDBG_ASSERT(pvInBuff); SPDBG_ASSERT(iNumSamples); // Apply Volume if ( SUCCEEDED(hr) && dGain != 1.0 ) { // NOTE THAT REGISTRY GAIN VALUE MAY BE GREATER THAN 1. // Make sure it is in bounds of SHRT_MAX, since we'll be multiplying it times samples if ( dGain > SHRT_MAX ) { dGain = SHRT_MAX; } else if ( dGain < 0 ) // gain should never be < 0. { dGain = 1.0; } long lGain = ( dGain * (1 << g_iBase) ); if ( m_pOutputFormat->wFormatTag == WAVE_FORMAT_ALAW || m_pOutputFormat->wFormatTag == WAVE_FORMAT_MULAW ) { short* pnBuff = NULL; // need to convert samples int iOriginalFormatType = VapiIO::TypeOf (m_pOutputFormat); if ( iOriginalFormatType == -1 ) { hr = E_FAIL; } // Allocate the intermediate buffer if ( SUCCEEDED(hr) ) { pnBuff = new short[iNumSamples]; if ( !pnBuff ) { hr = E_OUTOFMEMORY; } } // Allocate the final (out) buffer if ( SUCCEEDED(hr) ) { *ppvOutBuff = new char[iNumSamples * VapiIO::SizeOf(iOriginalFormatType)]; if ( !*ppvOutBuff ) { hr = E_OUTOFMEMORY; } } // Convert to something we can use if ( SUCCEEDED(hr) ) { if ( 0 == VapiIO::DataFormatConversion ((char *)pvInBuff, iOriginalFormatType, (char*)pnBuff, VAPI_PCM16, iNumSamples) ) { hr = E_FAIL; } } // Apply gain if ( SUCCEEDED(hr) ) { double dSample = 0; for ( i=0; i LONG_MAX ) { pnBuff[i] = (short) (LONG_MAX >> g_iBase); } else if ( dSample < LONG_MIN ) { pnBuff[i] = (short) (LONG_MIN >> g_iBase); } else { pnBuff[i] = (short) ( ( pnBuff[i] * lGain ) >> g_iBase ); } } } // convert it back (from intermediate buff to final out buff) if ( SUCCEEDED(hr) ) { if ( 0 == VapiIO::DataFormatConversion ((char *)pnBuff, VAPI_PCM16, (char*)*ppvOutBuff, iOriginalFormatType, iNumSamples) ) { hr = E_FAIL; } } if ( pnBuff ) { delete [] pnBuff; pnBuff = NULL; } } else if ( m_pOutputFormat->wFormatTag == WAVE_FORMAT_PCM ) { double dSample = 0; // no converting necessary switch ( m_pOutputFormat->nBlockAlign ) { case 1: for ( i=0; i LONG_MAX ) { ((char*)pvInBuff)[i] = (char) (LONG_MAX >> g_iBase); } else if ( dSample < LONG_MIN ) { ((char*)pvInBuff)[i] = (char) (LONG_MIN >> g_iBase); } else { ((char*)pvInBuff)[i] = (char) ( ( ((char*)pvInBuff)[i] * lGain ) >> g_iBase ); } } } break; case 2: for ( i=0; i LONG_MAX ) { ((short*)pvInBuff)[i] = (short) (LONG_MAX >> g_iBase); } else if ( dSample < LONG_MIN ) { ((short*)pvInBuff)[i] = (short) (LONG_MIN >> g_iBase); } else { ((short*)pvInBuff)[i] = (short) ( ( ((short*)pvInBuff)[i] * lGain ) >> g_iBase ); } } break; default: hr = E_FAIL; } } else { hr = E_FAIL; } } if ( FAILED(hr) ) { if ( *ppvOutBuff ) { delete [] *ppvOutBuff; *ppvOutBuff = NULL; } } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::ReadEntry // // Reads a Db entry from the Db file. // /////////////////////////////////////////////////////// JOEM 3-2000 // HRESULT CPromptDb::ReadEntry(FILE* fp, CPromptEntry** ppEntry) { SPDBG_FUNC( "CPromptDb::ReadEntry" ); HRESULT hr = S_OK; WCHAR* entryInfo = NULL; WCHAR* ptr = NULL; WCHAR* tmpTag = NULL; WCHAR* start = NULL; SHORT state = 0; double dEntryInfo = 0.0; WCHAR line[1024]; WCHAR fullPath[_MAX_PATH+1] = L""; WCHAR dir[_MAX_DIR] = L""; bool fId = false; bool fFile = false; bool fText = false; bool fOriginal = false; bool fFrom = false; bool fTo = false; bool fStart = false; bool fEnd = false; bool fLeft = false; bool fRight = false; SPDBG_ASSERT (fp); *ppEntry = new CPromptEntry; if (!*ppEntry) { hr = E_OUTOFMEMORY; } while ( ( state != -1 ) && SUCCEEDED(hr) ) { if ( !fgetws(line, 1024, fp) ) { if (state == 0) { (*ppEntry)->Release(); *ppEntry = NULL; hr = S_FALSE; break; /* No more entries to read */ } else { hr = PEERR_DB_BAD_FORMAT; /* EOF in the middle of reading an entry*/ } } if ( SUCCEEDED(hr) ) { // Strip off the newline character if (line[wcslen(line)-1] == L'\n') { line[wcslen(line)-1] = L'\0'; } // Line ends when a comment marker is found if ( (ptr = wcschr (line, L'#')) != NULL ) { *ptr = L'\0'; } ptr = line; WSkipWhiteSpace (ptr); } if ( SUCCEEDED(hr) ) { while ( SUCCEEDED(hr) && *ptr ) { switch (state) { case 0: // Search for the starting tag // Discard everything up to the starting tag if ( wcsncmp( ptr, ENTRY_TAG, sizeof(ENTRY_TAG)/sizeof(ENTRY_TAG[0]) - 1 ) != 0) { hr = PEERR_DB_BAD_FORMAT; } if ( SUCCEEDED (hr) ) { ptr += wcslen (ENTRY_TAG); state = 1; } break; case 1: WSkipWhiteSpace (ptr); if (*ptr) { if ( wcsncmp(ptr, ENTRY_START, sizeof(ENTRY_START)/sizeof(ENTRY_START[0]) - 1 ) ==0 ) { ptr += wcslen (ENTRY_START); state = 2; } else { hr = PEERR_DB_BAD_FORMAT; } } break; case 2: // Only one item per line if ( (start = wcsstr(ptr, ENTRY_END)) != NULL) { // If we find the entry_end marker, this is the last // line. Remember, this line could have an item also *start = L'\0'; state = -1; } WSkipWhiteSpace (ptr); if (!*ptr) { break; } // Must be one of this elements of the entry, // otherwise, error hr = ExtractString (ptr, ID_TAG, &entryInfo); if ( SUCCEEDED (hr) ) { if ( fId ) { hr = PEERR_DB_BAD_FORMAT; //SPDBG_ASSERT (! "Duplicate ID field in database entry."); } if ( SUCCEEDED(hr) ) { hr = (*ppEntry)->SetId(entryInfo); } fId = true; ptr = L""; free(entryInfo); entryInfo = NULL; break; } hr = ExtractString (ptr, TEXT_TAG, &entryInfo); if ( SUCCEEDED (hr) ) { if ( fText ) { hr = PEERR_DB_BAD_FORMAT; //SPDBG_ASSERT (! "Duplicate TEXT field in database entry."); } if ( SUCCEEDED(hr) ) { hr = (*ppEntry)->SetText(entryInfo); } fText = true; ptr = L""; free(entryInfo); entryInfo = NULL; break; } hr = ExtractString (ptr, ORIGINAL_TAG, &entryInfo); if ( SUCCEEDED (hr) ) { if ( fOriginal ) { hr = PEERR_DB_BAD_FORMAT; //SPDBG_ASSERT (! "Duplicate TEXT field in database entry."); } if ( SUCCEEDED(hr) ) { hr = (*ppEntry)->SetOriginalText(entryInfo); } fOriginal = true; ptr = L""; free(entryInfo); entryInfo = NULL; break; } hr = ExtractString (ptr, FILE_TAG, &entryInfo); if ( SUCCEEDED (hr) ) { if ( fFile ) { hr = PEERR_DB_BAD_FORMAT; //SPDBG_ASSERT (! "Duplicate FILE NAME field in database entry."); } if ( SUCCEEDED(hr) ) { if ( entryInfo ) { // Is it a full path? (i.e, does it start with a "\" or is 2nd char a colon) if ( wcslen(entryInfo) >= 2 && ( entryInfo[0] == L'\\' || entryInfo[1] == L':' ) ) { hr = (*ppEntry)->SetFileName(entryInfo); } else // must be a relative path { // Construct the full path to the entry _wsplitpath(m_pActiveDb->pszPathName, fullPath, dir, NULL, NULL); wcscat(fullPath, dir); wcscat(fullPath, entryInfo); // Don't check this path here. We don't want Db loading to fail // if a wav doesn't exist -- load the db anyway, and later when // searching db, we check the paths and resort to TTS if path is invalid. hr = (*ppEntry)->SetFileName(fullPath); } } } fFile = true; ptr = L""; free(entryInfo); entryInfo = NULL; break; } hr = ExtractString (ptr, TAG_TAG, &entryInfo); if ( SUCCEEDED (hr) ) { hr = (*ppEntry)->AddTag(entryInfo); ptr = L""; free(entryInfo); entryInfo = NULL; break; } hr = ExtractString (ptr, START_PHONE_TAG, &entryInfo); if ( SUCCEEDED (hr) ) { if ( fStart ) { hr = PEERR_DB_BAD_FORMAT; //SPDBG_ASSERT (! "Duplicate START PHONE field in database entry."); } if ( SUCCEEDED(hr) ) { hr = (*ppEntry)->SetStartPhone(entryInfo); } fStart = true; ptr = L""; free(entryInfo); entryInfo = NULL; break; } hr = ExtractString (ptr, END_PHONE_TAG, &entryInfo); if ( SUCCEEDED (hr) ) { if ( fEnd ) { hr = PEERR_DB_BAD_FORMAT; //SPDBG_ASSERT (! "Duplicate END PHONE field in database entry."); } if ( SUCCEEDED(hr) ) { hr = (*ppEntry)->SetEndPhone(entryInfo); } fEnd = true; ptr = L""; free(entryInfo); entryInfo = NULL; break; } hr = ExtractString (ptr, RIGHT_CONTEXT_TAG, &entryInfo); if ( SUCCEEDED (hr) ) { if ( fRight ) { hr = PEERR_DB_BAD_FORMAT; //SPDBG_ASSERT (! "Duplicate RIGHT CONTEXT field in database entry."); } if ( SUCCEEDED(hr) ) { hr = (*ppEntry)->SetRightContext(entryInfo); } fRight = true; ptr = L""; free(entryInfo); entryInfo = NULL; break; } hr = ExtractString (ptr, LEFT_CONTEXT_TAG, &entryInfo); if ( SUCCEEDED (hr) ) { if ( fLeft ) { hr = PEERR_DB_BAD_FORMAT; //SPDBG_ASSERT (! "Duplicate LEFT CONTEXT field in database entry."); } if ( SUCCEEDED(hr) ) { hr = (*ppEntry)->SetLeftContext(entryInfo); } fLeft = true; ptr = L""; free(entryInfo); entryInfo = NULL; break; } hr = ExtractDouble (ptr, FROM_TAG, &dEntryInfo); if ( SUCCEEDED (hr) ) { if ( fFrom ) { hr = PEERR_DB_BAD_FORMAT; //SPDBG_ASSERT (! "Duplicate FROM field in database entry."); } if ( SUCCEEDED(hr) ) { hr = (*ppEntry)->SetStart(dEntryInfo); } fFrom = true; ptr = L""; break; } hr = ExtractDouble (ptr, TO_TAG, &dEntryInfo); if ( SUCCEEDED (hr) ) { if ( fTo ) { hr = PEERR_DB_BAD_FORMAT; //SPDBG_ASSERT (! "Duplicate TO field in database entry."); } if ( SUCCEEDED(hr) ) { hr = (*ppEntry)->SetEnd(dEntryInfo); } fTo = true; ptr = L""; break; } hr = PEERR_DB_BAD_FORMAT; } // switch (state) } // while (*ptr) } } // while ( SUCCEEDED(hr) && state != -1) // THESE ARE THE MANDATORY FIELDS FOR THE DATABASE ENTRY if ( *ppEntry && SUCCEEDED(hr) ) { if ( !fId ) { //SPDBG_ASSERT (! "Missing ID field in database entry."); hr = PEERR_DB_BAD_FORMAT; } if ( !fFile ) { //SPDBG_ASSERT (! "Missing FILE field in database entry."); hr = PEERR_DB_BAD_FORMAT; } if ( !fText ) { //SPDBG_ASSERT (! "Missing TEXT field in database entry."); hr = PEERR_DB_BAD_FORMAT; } if ( !fFrom ) { //SPDBG_ASSERT (! "Missing FROM field in database entry."); hr = PEERR_DB_BAD_FORMAT; } if ( !fTo ) { //SPDBG_ASSERT (! "Missing TO field in database entry."); hr = PEERR_DB_BAD_FORMAT; } } // From is less than To? if ( *ppEntry && SUCCEEDED(hr) ) { double from = 0.0; double to = 0.0; hr = (*ppEntry)->GetStart(&from); if ( SUCCEEDED(hr) ) { hr = (*ppEntry)->GetEnd(&to); } if ( SUCCEEDED(hr) ) { if ( to <= from ) { // a value of -1.0 is ok for "to" -- it means play to end of file. if ( to != -1.0 ) { hr = PEERR_DB_BAD_FORMAT; } } } } if ( FAILED(hr) && *ppEntry ) { (*ppEntry)->Release(); *ppEntry = NULL; } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::WriteEntry // // Writes a formated entry to a Db file. // /////////////////////////////////////////////////////// JOEM 3-2000 // HRESULT CPromptDb::WriteEntry(FILE *fp, IPromptEntry *pIPE) { SPDBG_FUNC( "CPromptDb::WriteEntry" ); HRESULT hr = S_OK; const WCHAR* entryText = NULL; SPDBG_ASSERT (fp); SPDBG_ASSERT (pIPE); fwprintf (fp, L"%s %s\n", ENTRY_TAG, ENTRY_START); if ( SUCCEEDED(hr) ) { hr = pIPE->GetId(&entryText); if ( SUCCEEDED(hr) ) { fwprintf (fp, L"\t%s %s\n", ID_TAG, entryText); entryText = NULL; } } if ( SUCCEEDED(hr) ) { hr = pIPE->GetText(&entryText); if ( SUCCEEDED(hr) ) { fwprintf (fp, L"\t%s %s\n", TEXT_TAG, entryText); entryText = NULL; } } if ( SUCCEEDED(hr) ) { hr = pIPE->GetOriginalText(&entryText); if ( SUCCEEDED(hr) ) { fwprintf (fp, L"\t%s %s\n", ORIGINAL_TAG, entryText); entryText = NULL; } } if ( SUCCEEDED(hr) ) { USHORT entryTagCount = 0; hr = pIPE->CountTags(&entryTagCount); if ( SUCCEEDED(hr) && entryTagCount ) { for ( USHORT i=0; i < entryTagCount; i++) { hr = pIPE->GetTag(&entryText, i); if ( SUCCEEDED(hr) ) { fwprintf( fp, L"\t%s %s\n", TAG_TAG, entryText ); entryText = NULL; } } } } if ( SUCCEEDED(hr) ) { hr = pIPE->GetFileName(&entryText); if ( SUCCEEDED(hr) ) { fwprintf (fp, L"\t%s %s\n", FILE_TAG, entryText); entryText = NULL; } } if ( SUCCEEDED(hr) ) { double start = 0.0; hr = pIPE->GetStart(&start); if ( SUCCEEDED(hr) ) { fwprintf( fp, L"\t%s %f\n", FROM_TAG, start); } } if ( SUCCEEDED(hr) ) { double end = 0.0; hr = pIPE->GetEnd(&end); if ( SUCCEEDED(hr) ) { fwprintf( fp, L"\t%s %f\n", TO_TAG, end); } } fwprintf (fp, L"%s\n\n", ENTRY_END); SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::DuplicateEntry // // /////////////////////////////////////////////////////// JOEM 3-2000 // CPromptEntry* CPromptDb::DuplicateEntry(const CPromptEntry *oldEntry) { SPDBG_FUNC( "CPromptDb::DuplicateEntry" ); CPromptEntry* newEntry = new CPromptEntry(*oldEntry); return newEntry; } ////////////////////////////////////////////////////////////////////// // CPromptDb::ExtractString // // /////////////////////////////////////////////////////// JOEM 3-2000 // HRESULT CPromptDb::ExtractString(WCHAR *line, const WCHAR *tag, WCHAR **value) { SPDBG_FUNC( "CPromptDb::ExtractString" ); HRESULT hr = S_OK; int tagLen = 0; int entryLen = 0; tagLen = (USHORT) wcslen (tag); if (wcsncmp(line, tag, tagLen)==0) { line += tagLen; WSkipWhiteSpace (line); for (entryLen = wcslen(line) -1; entryLen>=0 && iswspace(line[entryLen]); entryLen--) { line[entryLen] = L'\0'; } // Control characters are not allowed in Db if ( FindUnicodeControlChar(line) ) { hr = E_UNEXPECTED; } if ( SUCCEEDED(hr) && !wcslen(line) ) { hr = E_INVALIDARG; } if ( SUCCEEDED(hr) ) { if ((*value = _wcsdup(line)) == NULL) { hr = E_OUTOFMEMORY; } } } else { hr = E_FAIL; } // Don't report failures here - this func is allowed to fail frequently. //SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::ExtractDouble // // /////////////////////////////////////////////////////// JOEM 3-2000 // HRESULT CPromptDb::ExtractDouble(WCHAR *line, const WCHAR *tag, double *value) { SPDBG_FUNC( "CPromptDb::ExtractDouble" ); HRESULT hr = S_OK; int tagLen = 0; int entryLen = 0; WCHAR* psz = NULL; *value = 0.0; tagLen = (USHORT) wcslen (tag); if (wcsncmp(line, tag, tagLen)==0) { line += tagLen; WSkipWhiteSpace (line); for (entryLen = wcslen(line) -1; entryLen>=0 && iswspace(line[entryLen]); entryLen--) { line[entryLen] = L'\0'; } // Control characters are not allowed in Db if ( FindUnicodeControlChar(line) ) { hr = E_UNEXPECTED; } if ( SUCCEEDED(hr) && !wcslen(line) ) { hr = E_INVALIDARG; } // make sure it's a number if ( SUCCEEDED(hr) ) { psz = line; while ( SUCCEEDED(hr) && psz[0] ) { if ( iswdigit(psz[0]) || psz[0] == L'.' || psz[0] == L'-' ) { psz++; } else { hr = E_INVALIDARG; } } } if ( SUCCEEDED(hr) ) { *value = wcstod( line, NULL ); } if ( SUCCEEDED(hr) && *value < 0 && *value != -1.0 ) { hr = E_INVALIDARG; } } else { hr = E_FAIL; } // Don't report failures here - this func is allowed to fail frequently. //SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::TempName // // Creates a temporary file name for the active db. // /////////////////////////////////////////////////////// JOEM 3-2000 // HRESULT CPromptDb::TempName() { SPDBG_FUNC( "CPromptDb::TempName" ); HRESULT hr = S_OK; WCHAR nameStr[256]; SPDBG_ASSERT( m_pActiveDb ); SPDBG_ASSERT( m_pActiveDb->pszPathName ); wcscat( wcscpy( nameStr, m_pActiveDb->pszPathName ), L".tmp" ); if ( m_pActiveDb->pszTempName && (wcscmp( m_pActiveDb->pszTempName, nameStr ) != 0) ) { free (m_pActiveDb->pszTempName); m_pActiveDb->pszTempName = NULL; } if ( !m_pActiveDb->pszTempName ) { if ( ( m_pActiveDb->pszTempName = _wcsdup(nameStr) ) == NULL) { hr = E_OUTOFMEMORY; } } SPDBG_REPORT_ON_FAIL( hr ); return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::IndexTextHash // // Makes a text hash. Key: text // Value: list of IDs for that text // /////////////////////////////////////////////////////// JOEM 3-2000 // HRESULT CPromptDb::IndexTextHash() { SPDBG_FUNC( "CPromptDb::IndexTextHash" ); HRESULT hr = S_OK; WCHAR* id = NULL; const WCHAR* entryInfo = NULL; USHORT idx1 = 0; USHORT idx2 = 0; USHORT i = 0; USHORT found = 0; CPromptEntry* entry = NULL; CDynStrArray* candList = NULL; while ( SUCCEEDED(hr) ) { m_pActiveDb->idHash.NextKey (&idx1, &idx2, &id); if ( !id ) { // this was the last one break; } if ( SUCCEEDED(hr) ) { entry = (CPromptEntry*) m_pActiveDb->idHash.Find(id); if ( !entry ) { hr = E_UNEXPECTED; } } if ( SUCCEEDED(hr) ) { hr = entry->GetText(&entryInfo); if ( SUCCEEDED(hr) ) { // Is there already a list of entries for this text? If not, create one. if ( (candList = (CDynStrArray*) m_pActiveDb->textHash.Find(entryInfo)) == NULL ) { candList = new CDynStrArray; if (!candList) { hr = E_OUTOFMEMORY; } if ( SUCCEEDED(hr) ) { hr = m_pActiveDb->textHash.BuildEntry(entryInfo, candList); } } } entryInfo = NULL; if ( SUCCEEDED(hr) ) { // Search the list, check that the entry doesn't exist found = 0; hr = entry->GetId(&entryInfo); if ( SUCCEEDED(hr) ) { for ( i = 0; i < candList->m_aDstr.GetSize(); i++ ) { if ( wcscmp(candList->m_aDstr[i].dstr, entryInfo) == 0 ) { found = 1; break; } } if ( !found ) { // Add item to the list, the list is already in the hash table candList->m_aDstr.Add(entryInfo); } entryInfo = NULL; } } entry = NULL; } // if ( SUCCEEDED(hr) ) } // while if ( FAILED(hr) ) { if ( candList ) { delete candList; candList = NULL; } } return hr; } ////////////////////////////////////////////////////////////////////// // CPromptDb::GetPromptFormat // // Gets the format of the first prompt file, used for setting the // output format. Note, however, that the application can change the // format, and that prompt files do not need to be all in the same // format. // /////////////////////////////////////////////////////// JOEM 3-2000 // STDMETHODIMP CPromptDb::GetPromptFormat (WAVEFORMATEX **ppwf) { HRESULT hr = E_FAIL; USHORT unId1 = 0; USHORT unId2 = 0; WCHAR *pszId = NULL; IPromptEntry* pIPE = NULL; while ( FAILED(hr) ) { hr = GetNextEntry(&unId1, &unId2, &pIPE); } if ( hr == S_OK ) { WAVEFORMATEX* newWF = (WAVEFORMATEX*)::CoTaskMemAlloc( sizeof(WAVEFORMATEX) ); if ( newWF ) { hr = pIPE->GetFormat( &newWF ); newWF->cbSize = 0; } else { hr = E_OUTOFMEMORY; } if ( SUCCEEDED(hr) ) { *ppwf = newWF; } } return hr; }