1289 lines
43 KiB
C++
1289 lines
43 KiB
C++
// 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 © Microsoft Corporation. All rights reserved
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
// DlgMain.CPP
|
|
//
|
|
// This module handles the main dialog box, and it's controls.
|
|
//
|
|
// The main dialog is actually the application's main window class, there
|
|
// is no conventional "Window class", only this dialog class which is
|
|
// registered in WinMain.
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "globals.h"
|
|
|
|
//
|
|
// Other local functions
|
|
//
|
|
inline void TTSAppStatusMessage( HWND hWnd, LPCTSTR szMessage );
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CTTSApp::DlgProcMain
|
|
// ---------------------------------------------------------------------------
|
|
// Description: Main window procedure.
|
|
// Arguments:
|
|
// HWND [in] Window handle.
|
|
// UINT [in] Message identifier.
|
|
// WPARAM [in] Depends on message.
|
|
// LPARAM [in] Depends on message.
|
|
// Returns:
|
|
// LPARAM Depends on message.
|
|
LRESULT CALLBACK CTTSApp::DlgProcMain(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
CTTSApp* pThis = (CTTSApp *)(LONG_PTR)::GetWindowLongPtr( hwnd, GWLP_USERDATA );
|
|
|
|
// Call the appropriate function to handle the messages
|
|
switch(uMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
::SetWindowLongPtr( hwnd, GWLP_USERDATA, (LONG_PTR)lParam );
|
|
pThis = (CTTSApp *)lParam;
|
|
return pThis->OnInitDialog( hwnd );
|
|
|
|
case WM_TTSAPPCUSTOMEVENT:
|
|
pThis->MainHandleSynthEvent();
|
|
return TRUE;
|
|
|
|
case WM_HSCROLL:
|
|
pThis->HandleScroll( (HWND) lParam );
|
|
return TRUE;
|
|
|
|
case WM_COMMAND:
|
|
if( pThis )
|
|
{
|
|
pThis->MainHandleCommand( (int)(LOWORD(wParam)), (HWND)lParam,
|
|
(UINT)HIWORD(wParam) );
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_CLOSE:
|
|
pThis->MainHandleClose();
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
HRESULT CTTSApp::InitSapi()
|
|
/////////////////////////////////////////////////////////////////
|
|
{
|
|
HRESULT hr;
|
|
|
|
hr = m_cpVoice.CoCreateInstance( CLSID_SpVoice );
|
|
|
|
return hr;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
HIMAGELIST CTTSApp::InitImageList()
|
|
/////////////////////////////////////////////////////////////////
|
|
{
|
|
HIMAGELIST hListBmp;
|
|
HBITMAP hBmp;
|
|
|
|
hListBmp = ImageList_Create( CHARACTER_WIDTH, CHARACTER_HEIGHT, ILC_COLOR32 | ILC_MASK, 1, 0 );
|
|
if (hListBmp)
|
|
{
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICFULL ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICMOUTH2 ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICMOUTH3 ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICMOUTH4 ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICMOUTH5 ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICMOUTH6 ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICMOUTH7 ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICMOUTH8 ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICMOUTH9 ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICMOUTH10 ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICMOUTH11 ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICMOUTH12 ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICMOUTH13 ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICEYESNAR ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
hBmp = LoadBitmap( m_hInst, MAKEINTRESOURCE( IDB_MICEYESCLO ) );
|
|
ImageList_AddMasked( hListBmp, hBmp, 0xff00ff );
|
|
DeleteObject( hBmp );
|
|
|
|
ImageList_SetOverlayImage( hListBmp, 1, 1 );
|
|
ImageList_SetOverlayImage( hListBmp, 2, 2 );
|
|
ImageList_SetOverlayImage( hListBmp, 3, 3 );
|
|
ImageList_SetOverlayImage( hListBmp, 4, 4 );
|
|
ImageList_SetOverlayImage( hListBmp, 5, 5 );
|
|
ImageList_SetOverlayImage( hListBmp, 6, 6 );
|
|
ImageList_SetOverlayImage( hListBmp, 7, 7 );
|
|
ImageList_SetOverlayImage( hListBmp, 8, 8 );
|
|
ImageList_SetOverlayImage( hListBmp, 9, 9 );
|
|
ImageList_SetOverlayImage( hListBmp, 10, 10 );
|
|
ImageList_SetOverlayImage( hListBmp, 11, 11 );
|
|
ImageList_SetOverlayImage( hListBmp, 12, 12 );
|
|
ImageList_SetOverlayImage( hListBmp, 13, 13 );
|
|
ImageList_SetOverlayImage( hListBmp, 14, WEYESNAR );
|
|
ImageList_SetOverlayImage( hListBmp, 15, WEYESCLO );
|
|
}
|
|
return hListBmp;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
BOOL CTTSApp::OnInitDialog( HWND hWnd )
|
|
/////////////////////////////////////////////////////////////////
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// Store this as the "Main Dialog"
|
|
m_hWnd = hWnd;
|
|
|
|
// Add some default text to the main edit control
|
|
SetDlgItemText( hWnd, IDE_EDITBOX, _T("Enter text you wish spoken here.") );
|
|
|
|
// Set the event mask in the rich edit control so that it notifies us when text is
|
|
// changed in the control
|
|
SendMessage( GetDlgItem( hWnd, IDE_EDITBOX ), EM_SETEVENTMASK, 0, ENM_CHANGE );
|
|
|
|
// Initialize the Output Format combo box
|
|
int i;
|
|
for( i=0; i<NUM_OUTPUTFORMATS; i++ )
|
|
{
|
|
SendDlgItemMessage( hWnd, IDC_COMBO_OUTPUT, CB_ADDSTRING, 0,
|
|
(LPARAM)g_aszOutputFormat[i] );
|
|
|
|
SendDlgItemMessage( hWnd, IDC_COMBO_OUTPUT, CB_SETITEMDATA, i,
|
|
(LPARAM)g_aOutputFormat[i] );
|
|
}
|
|
|
|
if ( !m_cpVoice )
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
// Set the default output format as the current selection.
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
CComPtr<ISpStreamFormat> cpStream;
|
|
HRESULT hrOutputStream = m_cpVoice->GetOutputStream(&cpStream);
|
|
|
|
if (hrOutputStream == S_OK)
|
|
{
|
|
CSpStreamFormat Fmt;
|
|
hr = Fmt.AssignFormat(cpStream);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
SPSTREAMFORMAT eFmt = Fmt.ComputeFormatEnum();
|
|
for( i=0; i<NUM_OUTPUTFORMATS; i++ )
|
|
{
|
|
if( g_aOutputFormat[i] == eFmt )
|
|
{
|
|
m_DefaultFormatIndex = i;
|
|
SendDlgItemMessage( hWnd, IDC_COMBO_OUTPUT, CB_SETCURSEL, m_DefaultFormatIndex, 0 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SendDlgItemMessage( hWnd, IDC_COMBO_OUTPUT, CB_SETCURSEL, 0, 0 );
|
|
}
|
|
}
|
|
|
|
// Use the SAPI5 helper function in sphelper.h to initialize the Voice combo box.
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
hr = SpInitTokenComboBox( GetDlgItem( hWnd, IDC_COMBO_VOICES ), SPCAT_VOICES );
|
|
}
|
|
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
SpCreateDefaultObjectFromCategoryId( SPCAT_AUDIOOUT, &m_cpOutAudio );
|
|
}
|
|
|
|
// Set default voice data
|
|
VoiceChange();
|
|
|
|
// Set Range for Skip Edit Box...
|
|
SendDlgItemMessage( hWnd, IDC_SKIP_SPIN, UDM_SETRANGE, TRUE, MAKELONG( 50, -50 ) );
|
|
|
|
// Set the notification message for the voice
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
m_cpVoice->SetNotifyWindowMessage( hWnd, WM_TTSAPPCUSTOMEVENT, 0, 0 );
|
|
}
|
|
|
|
// We're interested in all TTS events
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpVoice->SetInterest( SPFEI_ALL_TTS_EVENTS, SPFEI_ALL_TTS_EVENTS );
|
|
}
|
|
|
|
// Get default rate and volume
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpVoice->GetRate( &m_DefaultRate );
|
|
// initialize sliders and edit boxes with default rate
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
SendDlgItemMessage( hWnd, IDC_RATE_SLIDER, TBM_SETRANGE, TRUE, MAKELONG( SPMIN_RATE, SPMAX_RATE ) );
|
|
SendDlgItemMessage( hWnd, IDC_RATE_SLIDER, TBM_SETPOS, TRUE, m_DefaultRate );
|
|
SendDlgItemMessage( hWnd, IDC_RATE_SLIDER, TBM_SETPAGESIZE, TRUE, 5 );
|
|
}
|
|
}
|
|
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpVoice->GetVolume( &m_DefaultVolume );
|
|
// initialize sliders and edit boxes with default volume
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
SendDlgItemMessage( hWnd, IDC_VOLUME_SLIDER, TBM_SETRANGE, TRUE, MAKELONG( SPMIN_VOLUME, SPMAX_VOLUME ) );
|
|
SendDlgItemMessage( hWnd, IDC_VOLUME_SLIDER, TBM_SETPOS, TRUE, m_DefaultVolume );
|
|
SendDlgItemMessage( hWnd, IDC_VOLUME_SLIDER, TBM_SETPAGESIZE, TRUE, 10 );
|
|
}
|
|
}
|
|
|
|
// If any SAPI initialization failed, shut down!
|
|
if( FAILED( hr ) )
|
|
{
|
|
MessageBox( NULL, _T("Error initializing speech objects. Shutting down."), _T("Error"), MB_OK );
|
|
SendMessage( hWnd, WM_CLOSE, 0, 0 );
|
|
return(FALSE);
|
|
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Create the child windows to which we'll blit our result
|
|
//
|
|
HWND hCharWnd = GetDlgItem(hWnd, IDC_CHARACTER);
|
|
RECT rc;
|
|
|
|
GetClientRect(hCharWnd, &rc);
|
|
rc.left = (rc.right - CHARACTER_WIDTH) / 2;
|
|
rc.top = (rc.bottom - CHARACTER_HEIGHT) / 2;
|
|
m_hChildWnd = CreateWindow( CHILD_CLASS, NULL,
|
|
WS_CHILDWINDOW | WS_VISIBLE,
|
|
rc.left, rc.top,
|
|
rc.left + CHARACTER_WIDTH, rc.top + CHARACTER_HEIGHT,
|
|
hCharWnd, NULL, m_hInst, NULL );
|
|
|
|
if ( !m_hChildWnd )
|
|
{
|
|
MessageBox( hWnd, _T("Error initializing speech objects. Shutting down."), _T("Error"), MB_OK );
|
|
SendMessage( hWnd, WM_CLOSE, 0, 0 );
|
|
return(FALSE);
|
|
|
|
}
|
|
else
|
|
{
|
|
// Load Mouth Bitmaps and use and ImageList since we'll blit the mouth
|
|
// and eye positions over top of the full image
|
|
g_hListBmp = InitImageList();
|
|
}
|
|
}
|
|
return(TRUE);
|
|
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
void CTTSApp::Stop()
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// Resets global audio state to stopped, updates the Pause/Resume button
|
|
// and repaints the mouth in a closed position
|
|
//
|
|
{
|
|
// Stop current rendering with a PURGEBEFORESPEAK...
|
|
HRESULT hr = m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, 0 );
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Stop error\r\n") );
|
|
}
|
|
|
|
SetWindowText( GetDlgItem( m_hWnd, IDB_PAUSE ), _T("Pause") );
|
|
m_bPause = FALSE;
|
|
m_bStop = TRUE;
|
|
// Mouth closed
|
|
g_iBmp = 0;
|
|
SendMessage( m_hChildWnd, WM_PAINT, 0, 0 );
|
|
InvalidateRect( m_hChildWnd, NULL, FALSE );
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
void CTTSApp::MainHandleCommand( int id, HWND hWndControl, UINT codeNotify )
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// Handle each of the WM_COMMAND messages that come in, and deal with
|
|
// them appropriately
|
|
//
|
|
{
|
|
UINT cNumChar = 0;
|
|
HRESULT hr = S_OK;
|
|
TCHAR szAFileName[NORM_SIZE] = _T("");
|
|
static BOOL bIsUnicode = FALSE;
|
|
BOOL bWavFileOpened = FALSE;
|
|
LRESULT iFormat;
|
|
CComPtr<ISpStream> cpWavStream;
|
|
CComPtr<ISpStreamFormat> cpOldStream;
|
|
HWND hwndEdit;
|
|
BOOL bFileOpened = FALSE;
|
|
|
|
// Get handle to the main edit box
|
|
hwndEdit = GetDlgItem( m_hWnd, IDE_EDITBOX );
|
|
|
|
switch(id)
|
|
{
|
|
// About Box display
|
|
case IDC_ABOUT:
|
|
::DialogBox( m_hInst, (LPCTSTR)IDD_ABOUT, m_hWnd, (DLGPROC)About );
|
|
break;
|
|
|
|
// Any change to voices is sent to VoiceChange() function
|
|
case IDC_COMBO_VOICES:
|
|
if( codeNotify == CBN_SELCHANGE )
|
|
{
|
|
hr = VoiceChange();
|
|
}
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Error changing voices\r\n") );
|
|
}
|
|
|
|
break;
|
|
|
|
// If user wants to speak a file pop the standard windows open file
|
|
// dialog box and load the text into a global buffer (m_pszwFileText)
|
|
// which will be used when the user hits speak.
|
|
case IDB_OPEN:
|
|
bFileOpened = CallOpenFileDialog( szAFileName,
|
|
_T("TXT (*.txt)\0*.txt\0XML (*.xml)\0*.xml\0All Files (*.*)\0*.*\0") );
|
|
if( bFileOpened )
|
|
{
|
|
DWORD dwFileSize = 0;
|
|
|
|
wcscpy_s( m_szWFileName, _countof(m_szWFileName), CT2W( szAFileName ) );
|
|
ReadTheFile( szAFileName, &bIsUnicode, &m_pszwFileText );
|
|
|
|
if( bIsUnicode )
|
|
{
|
|
// Unicode source
|
|
UpdateEditCtlW( m_pszwFileText );
|
|
}
|
|
else
|
|
{
|
|
// MBCS source
|
|
#ifdef _UNICODE
|
|
LPTSTR pszFileText = _tcsdup( m_pszwFileText );
|
|
#else
|
|
// We're compiling ANSI, so we need to convert the string to MBCS
|
|
// Note that a W2T may not be good here, since this string might
|
|
// be very big
|
|
LPTSTR pszFileText = NULL;
|
|
int iNeeded = ::WideCharToMultiByte( CP_ACP, 0, m_pszwFileText, -1, NULL, 0, NULL, NULL );
|
|
pszFileText = (LPTSTR) ::malloc( sizeof( TCHAR ) * ( iNeeded + 1 ) );
|
|
::WideCharToMultiByte( CP_ACP, 0, m_pszwFileText, -1, pszFileText, iNeeded + 1, NULL, NULL );
|
|
#endif
|
|
if ( pszFileText )
|
|
{
|
|
SetDlgItemText( m_hWnd, IDE_EDITBOX, pszFileText );
|
|
free( pszFileText );
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wcscpy_s( m_szWFileName, _countof(m_szWFileName), L"" );
|
|
}
|
|
// Always SetFocus back to main edit window so text highlighting will work
|
|
SetFocus( hwndEdit );
|
|
break;
|
|
|
|
// Handle speak
|
|
case IDB_SPEAK:
|
|
HandleSpeak();
|
|
break;
|
|
|
|
case IDB_PAUSE:
|
|
if( !m_bStop )
|
|
{
|
|
if( !m_bPause )
|
|
{
|
|
SetWindowText( GetDlgItem( m_hWnd, IDB_PAUSE ), _T("Resume") );
|
|
// Pause the voice...
|
|
m_cpVoice->Pause();
|
|
m_bPause = TRUE;
|
|
TTSAppStatusMessage( m_hWnd, _T("Pause\r\n") );
|
|
}
|
|
else
|
|
{
|
|
SetWindowText( GetDlgItem( m_hWnd, IDB_PAUSE ), _T("Pause") );
|
|
m_cpVoice->Resume();
|
|
m_bPause = FALSE;
|
|
}
|
|
}
|
|
SetFocus( hwndEdit );
|
|
break;
|
|
|
|
case IDB_STOP:
|
|
TTSAppStatusMessage( m_hWnd, _T("Stop\r\n") );
|
|
// Set the global audio state to stop
|
|
Stop();
|
|
SetFocus( hwndEdit );
|
|
break;
|
|
|
|
case IDB_SKIP:
|
|
{
|
|
SetFocus( hwndEdit );
|
|
int fSuccess = false;
|
|
int SkipNum = GetDlgItemInt( m_hWnd, IDC_SKIP_EDIT, &fSuccess, true );
|
|
ULONG ulGarbage = 0;
|
|
WCHAR szGarbage[] = L"Sentence";
|
|
if ( fSuccess )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Skip\r\n") );
|
|
m_cpVoice->Skip( szGarbage, SkipNum, &ulGarbage );
|
|
}
|
|
else
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Skip failed\r\n") );
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IDE_EDITBOX:
|
|
// Set the global audio state to stop if user has changed contents of edit control
|
|
if( codeNotify == EN_CHANGE )
|
|
{
|
|
Stop();
|
|
}
|
|
break;
|
|
|
|
case IDB_SPEAKWAV:
|
|
bWavFileOpened = CallOpenFileDialog( szAFileName,
|
|
_T("WAV (*.wav)\0*.wav\0All Files (*.*)\0*.*\0") );
|
|
// Speak the wav file using SpeakStream
|
|
if( bWavFileOpened )
|
|
{
|
|
WCHAR szwWavFileName[NORM_SIZE] = L"";;
|
|
|
|
wcscpy_s( szwWavFileName, _countof(szwWavFileName), CT2W( szAFileName ) );
|
|
|
|
// User helper function found in sphelper.h to open the wav file and
|
|
// get back an IStream pointer to pass to SpeakStream
|
|
hr = SPBindToFile( szwWavFileName, SPFM_OPEN_READONLY, &cpWavStream );
|
|
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpVoice->SpeakStream( cpWavStream, SPF_ASYNC, NULL );
|
|
}
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Speak error\r\n") );
|
|
}
|
|
}
|
|
break;
|
|
|
|
// Reset all values to defaults
|
|
case IDB_RESET:
|
|
TTSAppStatusMessage( m_hWnd, _T("Reset\r\n") );
|
|
SendDlgItemMessage( m_hWnd, IDC_VOLUME_SLIDER, TBM_SETPOS, TRUE, m_DefaultVolume );
|
|
SendDlgItemMessage( m_hWnd, IDC_RATE_SLIDER, TBM_SETPOS, TRUE, m_DefaultRate );
|
|
SendDlgItemMessage( m_hWnd, IDC_SAVETOWAV, BM_SETCHECK, BST_UNCHECKED, 0 );
|
|
SendDlgItemMessage( m_hWnd, IDC_EVENTS, BM_SETCHECK, BST_UNCHECKED, 0 );
|
|
SetDlgItemText( m_hWnd, IDE_EDITBOX, _T("Enter text you wish spoken here.") );
|
|
|
|
// reset output format
|
|
SendDlgItemMessage( m_hWnd, IDC_COMBO_OUTPUT, CB_SETCURSEL, m_DefaultFormatIndex, 0 );
|
|
SendMessage( m_hWnd, WM_COMMAND, MAKEWPARAM(IDC_COMBO_OUTPUT, CBN_SELCHANGE), 0 );
|
|
|
|
// Change the volume and the rate to reflect what the UI says
|
|
HandleScroll( ::GetDlgItem( m_hWnd, IDC_VOLUME_SLIDER ) );
|
|
HandleScroll( ::GetDlgItem( m_hWnd, IDC_RATE_SLIDER ) );
|
|
|
|
SetFocus( hwndEdit );
|
|
break;
|
|
|
|
case IDC_COMBO_OUTPUT:
|
|
if( codeNotify == CBN_SELCHANGE )
|
|
{
|
|
// Get the audio output format and set it's GUID
|
|
iFormat = SendDlgItemMessage( m_hWnd, IDC_COMBO_OUTPUT, CB_GETCURSEL, 0, 0 );
|
|
SPSTREAMFORMAT eFmt = (SPSTREAMFORMAT)SendDlgItemMessage( m_hWnd, IDC_COMBO_OUTPUT,
|
|
CB_GETITEMDATA, iFormat, 0 );
|
|
CSpStreamFormat Fmt;
|
|
Fmt.AssignFormat(eFmt);
|
|
if ( m_cpOutAudio )
|
|
{
|
|
hr = m_cpOutAudio->SetFormat( Fmt.FormatId(), Fmt.WaveFormatExPtr() );
|
|
}
|
|
else
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
hr = m_cpVoice->SetOutput( m_cpOutAudio, FALSE );
|
|
}
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Format rejected\r\n") );
|
|
}
|
|
|
|
EnableSpeakButtons( SUCCEEDED( hr ) );
|
|
}
|
|
break;
|
|
|
|
case IDC_SAVETOWAV:
|
|
{
|
|
TCHAR szFileName[256];
|
|
_tcscpy_s(szFileName, _countof(szFileName), _T("\0"));
|
|
|
|
bFileOpened = CallSaveFileDialog( szFileName,
|
|
_T("WAV (*.wav)\0*.wav\0All Files (*.*)\0*.*\0") );
|
|
|
|
if (bFileOpened == FALSE) break;
|
|
|
|
wcscpy_s( m_szWFileName, _countof(m_szWFileName), CT2W(szFileName) );
|
|
|
|
CSpStreamFormat OriginalFmt;
|
|
hr = m_cpVoice->GetOutputStream( &cpOldStream );
|
|
if (hr == S_OK)
|
|
{
|
|
hr = OriginalFmt.AssignFormat(cpOldStream);
|
|
}
|
|
else
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
// User SAPI helper function in sphelper.h to create a wav file
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = SPBindToFile( m_szWFileName, SPFM_CREATE_ALWAYS, &cpWavStream, &OriginalFmt.FormatId(), OriginalFmt.WaveFormatExPtr() );
|
|
}
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
// Set the voice's output to the wav file instead of the speakers
|
|
hr = m_cpVoice->SetOutput(cpWavStream, TRUE);
|
|
}
|
|
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
// Do the Speak
|
|
HandleSpeak();
|
|
}
|
|
|
|
// Set output back to original stream
|
|
// Wait until the speak is finished if saving to a wav file so that
|
|
// the smart pointer cpWavStream doesn't get released before its
|
|
// finished writing to the wav.
|
|
m_cpVoice->WaitUntilDone( INFINITE );
|
|
cpWavStream.Release();
|
|
|
|
// Reset output
|
|
m_cpVoice->SetOutput( cpOldStream, FALSE );
|
|
|
|
TCHAR szTitle[MAX_PATH];
|
|
TCHAR szConfString[MAX_PATH];
|
|
if ( SUCCEEDED( hr ) )
|
|
{
|
|
LoadString( m_hInst, IDS_SAVE_NOTIFY, szConfString, MAX_PATH );
|
|
LoadString( m_hInst, IDS_NOTIFY_TITLE, szTitle, MAX_PATH );
|
|
MessageBox( m_hWnd, szConfString, szTitle, MB_OK | MB_ICONINFORMATION );
|
|
}
|
|
else
|
|
{
|
|
LoadString( m_hInst, IDS_SAVE_ERROR, szConfString, MAX_PATH );
|
|
MessageBox( m_hWnd, szConfString, NULL, MB_ICONEXCLAMATION );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CTTSApp::HandleSpeak()
|
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
|
{
|
|
HWND hwndEdit;
|
|
HRESULT hr = S_OK;
|
|
WCHAR *szWTextString = NULL;
|
|
|
|
// Get handle to the main edit box
|
|
hwndEdit = GetDlgItem( m_hWnd, IDE_EDITBOX );
|
|
|
|
TTSAppStatusMessage( m_hWnd, _T("Speak\r\n") );
|
|
SetFocus( hwndEdit );
|
|
m_bStop = FALSE;
|
|
|
|
// only get the string if we're not paused
|
|
if( !m_bPause )
|
|
{
|
|
// Find the length of the string in the buffer
|
|
GETTEXTLENGTHEX gtlx;
|
|
gtlx.codepage = 1200;
|
|
gtlx.flags = GTL_DEFAULT;
|
|
LRESULT lTextLen = SendDlgItemMessage( m_hWnd, IDE_EDITBOX, EM_GETTEXTLENGTHEX, (WPARAM) >lx, 0 );
|
|
szWTextString = new WCHAR[ lTextLen + 1 ];
|
|
|
|
GETTEXTEX GetText;
|
|
|
|
GetText.cb = (DWORD)((lTextLen + 1) * sizeof( WCHAR ));
|
|
GetText.codepage = 1200;
|
|
GetText.flags = GT_DEFAULT;
|
|
GetText.lpDefaultChar = NULL;
|
|
GetText.lpUsedDefChar = NULL;
|
|
|
|
// Get the string in a unicode buffer
|
|
SendDlgItemMessage( m_hWnd, IDE_EDITBOX, EM_GETTEXTEX, (WPARAM)&GetText, (LPARAM)szWTextString );
|
|
|
|
// do we speak or interpret the XML
|
|
hr = m_cpVoice->Speak( szWTextString, SPF_ASYNC | ((IsDlgButtonChecked( m_hWnd, IDC_SPEAKXML )) ? SPF_IS_XML : SPF_IS_NOT_XML), 0 );
|
|
|
|
delete[] szWTextString;
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Speak error\r\n") );
|
|
}
|
|
}
|
|
m_bPause = FALSE;
|
|
SetWindowText( GetDlgItem( m_hWnd, IDB_PAUSE ), _T("Pause") );
|
|
SetFocus( hwndEdit );
|
|
// Set state to run
|
|
hr = m_cpVoice->Resume();
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Speak error\r\n") );
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
void CTTSApp::MainHandleSynthEvent()
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// Handles the WM_TTSAPPCUSTOMEVENT application defined message and all
|
|
// of it's appropriate SAPI5 events.
|
|
//
|
|
{
|
|
|
|
CSpEvent event; // helper class in sphelper.h for events that releases any
|
|
// allocated memory in it's destructor - SAFER than SPEVENT
|
|
SPVOICESTATUS Stat;
|
|
WPARAM nStart;
|
|
LPARAM nEnd;
|
|
int i = 0;
|
|
HRESULT hr = S_OK;
|
|
|
|
while( event.GetFrom(m_cpVoice) == S_OK )
|
|
{
|
|
switch( event.eEventId )
|
|
{
|
|
case SPEI_START_INPUT_STREAM:
|
|
if( IsDlgButtonChecked( m_hWnd, IDC_EVENTS ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("StartStream event\r\n") );
|
|
}
|
|
break;
|
|
|
|
case SPEI_END_INPUT_STREAM:
|
|
// Set global boolean stop to TRUE when finished speaking
|
|
m_bStop = TRUE;
|
|
// Highlight entire text
|
|
nStart = 0;
|
|
nEnd = SendDlgItemMessage( m_hWnd, IDE_EDITBOX, WM_GETTEXTLENGTH, 0, 0 );
|
|
SendDlgItemMessage( m_hWnd, IDE_EDITBOX, EM_SETSEL, nStart, nEnd );
|
|
// Mouth closed
|
|
g_iBmp = 0;
|
|
InvalidateRect( m_hChildWnd, NULL, FALSE );
|
|
if( IsDlgButtonChecked( m_hWnd, IDC_EVENTS ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("EndStream event\r\n") );
|
|
}
|
|
break;
|
|
|
|
case SPEI_VOICE_CHANGE:
|
|
if( IsDlgButtonChecked( m_hWnd, IDC_EVENTS ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Voicechange event\r\n") );
|
|
}
|
|
break;
|
|
|
|
case SPEI_TTS_BOOKMARK:
|
|
if( IsDlgButtonChecked( m_hWnd, IDC_EVENTS ) )
|
|
{
|
|
// Get the string associated with the bookmark
|
|
// and add the null terminator.
|
|
TCHAR szBuff2[MAX_PATH] = _T("Bookmark event: ");
|
|
|
|
size_t cEventString = wcslen( event.String() ) + 1;
|
|
WCHAR *pwszEventString = new WCHAR[ cEventString ];
|
|
if ( pwszEventString )
|
|
{
|
|
wcscpy_s( pwszEventString, cEventString, event.String() );
|
|
_tcscat_s( szBuff2, _countof(szBuff2), CW2T(pwszEventString) );
|
|
delete[] pwszEventString;
|
|
}
|
|
|
|
_tcscat_s( szBuff2, _countof(szBuff2), _T("\r\n") );
|
|
TTSAppStatusMessage( m_hWnd, szBuff2 );
|
|
}
|
|
break;
|
|
|
|
case SPEI_WORD_BOUNDARY:
|
|
hr = m_cpVoice->GetStatus( &Stat, NULL );
|
|
if( FAILED( hr ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Voice GetStatus error\r\n") );
|
|
}
|
|
|
|
// Highlight word
|
|
nStart = (LPARAM)( Stat.ulInputWordPos / sizeof(char) );
|
|
nEnd = nStart + Stat.ulInputWordLen;
|
|
SendDlgItemMessage( m_hWnd, IDE_EDITBOX, EM_SETSEL, nStart, nEnd );
|
|
if( IsDlgButtonChecked( m_hWnd, IDC_EVENTS ) )
|
|
{
|
|
|
|
TTSAppStatusMessage( m_hWnd, _T("Wordboundary event\r\n") );
|
|
}
|
|
break;
|
|
|
|
case SPEI_PHONEME:
|
|
if( IsDlgButtonChecked( m_hWnd, IDC_EVENTS ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Phoneme event\r\n") );
|
|
}
|
|
break;
|
|
|
|
case SPEI_VISEME:
|
|
// Get the current mouth viseme position and map it to one of the
|
|
// 7 mouth bitmaps.
|
|
g_iBmp = g_aMapVisemeToImage[event.Viseme()]; // current viseme
|
|
|
|
InvalidateRect( m_hChildWnd, NULL, FALSE );
|
|
if( IsDlgButtonChecked( m_hWnd, IDC_EVENTS ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Viseme event\r\n") );
|
|
}
|
|
break;
|
|
|
|
case SPEI_SENTENCE_BOUNDARY:
|
|
if( IsDlgButtonChecked( m_hWnd, IDC_EVENTS ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Sentence event\r\n") );
|
|
}
|
|
break;
|
|
|
|
case SPEI_TTS_AUDIO_LEVEL:
|
|
if( IsDlgButtonChecked( m_hWnd, IDC_EVENTS ) )
|
|
{
|
|
WCHAR wszBuff[MAX_PATH];
|
|
swprintf_s(wszBuff, _countof(wszBuff), L"Audio level: %d\r\n", (ULONG)event.wParam);
|
|
TTSAppStatusMessage( m_hWnd, CW2T(wszBuff) );
|
|
}
|
|
break;
|
|
|
|
case SPEI_TTS_PRIVATE:
|
|
if( IsDlgButtonChecked( m_hWnd, IDC_EVENTS ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Private engine event\r\n") );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
TTSAppStatusMessage( m_hWnd, _T("Unknown message\r\n") );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
void CTTSApp::HandleScroll( HWND hCtl )
|
|
/////////////////////////////////////////////////////////////////////////
|
|
{
|
|
static int hpos = 1;
|
|
HWND hVolume, hRate;
|
|
HRESULT hr = S_OK;
|
|
|
|
// Get the current position of the slider
|
|
hpos = (int)SendMessage( hCtl, TBM_GETPOS, 0, 0 );
|
|
|
|
// Get the Handle for the scroll bar so it can be associated with an edit box
|
|
hVolume = GetDlgItem( m_hWnd, IDC_VOLUME_SLIDER );
|
|
hRate = GetDlgItem( m_hWnd, IDC_RATE_SLIDER );
|
|
|
|
if( hCtl == hVolume )
|
|
{
|
|
hr = m_cpVoice->SetVolume((USHORT)hpos);
|
|
}
|
|
else if( hCtl == hRate )
|
|
{
|
|
hr = m_cpVoice->SetRate(hpos);
|
|
}
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
TTSAppStatusMessage( m_hWnd, _T("Error setting volume / rate\r\n") );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
void CTTSApp::MainHandleClose()
|
|
/////////////////////////////////////////////////////////////////
|
|
{
|
|
// Call helper functions from sphelper.h to destroy combo boxes
|
|
// created with SpInitTokenComboBox
|
|
SpDestroyTokenComboBox( GetDlgItem( m_hWnd, IDC_COMBO_VOICES ) );
|
|
|
|
// Terminate the app
|
|
PostQuitMessage(0);
|
|
|
|
// Return success
|
|
return;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
HRESULT CTTSApp::VoiceChange()
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// This function is called during initialization and whenever the
|
|
// selection for the voice combo box changes.
|
|
// It gets the token pointer associated with the voice.
|
|
// If the new voice is different from the one that's currently
|
|
// selected, it first stops any synthesis that is going on and
|
|
// sets the new voice on the global voice object.
|
|
//
|
|
{
|
|
HRESULT hr = S_OK;
|
|
GUID* pguidAudioFormat = NULL;
|
|
|
|
// Get the token associated with the selected voice
|
|
ISpObjectToken* pToken = SpGetCurSelComboBoxToken( GetDlgItem( m_hWnd, IDC_COMBO_VOICES ) );
|
|
|
|
//Determine if it is the current voice
|
|
CComPtr<ISpObjectToken> pOldToken;
|
|
hr = m_cpVoice->GetVoice( &pOldToken );
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (pOldToken != pToken)
|
|
{
|
|
// Stop speaking. This is not necesary, for the next call to work,
|
|
// but just to show that we are changing voices.
|
|
hr = m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, 0);
|
|
// And set the new voice on the global voice object
|
|
if (SUCCEEDED (hr) )
|
|
{
|
|
hr = m_cpVoice->SetVoice( pToken );
|
|
}
|
|
}
|
|
}
|
|
|
|
EnableSpeakButtons( SUCCEEDED( hr ) );
|
|
|
|
return hr;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
BOOL CTTSApp::CallOpenFileDialog( __in LPTSTR szFileName, LPCTSTR szFilter )
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// Display the open dialog box to retrieve the user-selected
|
|
// .txt or .xml file for synthisizing
|
|
{
|
|
OPENFILENAME ofn;
|
|
BOOL bRetVal = TRUE;
|
|
LONG lRetVal;
|
|
HKEY hkResult;
|
|
TCHAR szPath[256] = _T("");
|
|
DWORD size = 256;
|
|
|
|
// Open the last directory used by this app (stored in registry)
|
|
lRetVal = RegCreateKeyEx( HKEY_CURRENT_USER,
|
|
_T("SOFTWARE\\Microsoft\\Speech\\AppData\\TTSApp"), 0, NULL, 0,
|
|
KEY_ALL_ACCESS, NULL, &hkResult, NULL );
|
|
|
|
if( lRetVal == ERROR_SUCCESS )
|
|
{
|
|
RegQueryValueEx( hkResult, _T("TTSFiles"), NULL, NULL, (PBYTE)szPath, &size );
|
|
}
|
|
|
|
size_t ofnsize = (BYTE*)&ofn.lpTemplateName + sizeof(ofn.lpTemplateName) - (BYTE*)&ofn;
|
|
ZeroMemory( &ofn, ofnsize);
|
|
|
|
|
|
ofn.lStructSize = (DWORD)ofnsize;
|
|
ofn.hwndOwner = m_hWnd;
|
|
ofn.lpstrFilter = szFilter;
|
|
ofn.lpstrCustomFilter = NULL;
|
|
ofn.nFilterIndex = 1;
|
|
ofn.lpstrInitialDir = szPath;
|
|
ofn.lpstrFile = szFileName;
|
|
ofn.nMaxFile = 256;
|
|
ofn.lpstrTitle = NULL;
|
|
ofn.lpstrFileTitle = NULL;
|
|
ofn.lpstrDefExt = NULL;
|
|
ofn.Flags = OFN_FILEMUSTEXIST | OFN_READONLY | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
|
|
|
|
// Pop the dialog
|
|
bRetVal = GetOpenFileName( &ofn );
|
|
|
|
// Write the directory path you're in to the registry
|
|
TCHAR pathstr[256] = _T("");
|
|
_tcscpy_s( pathstr, _countof(pathstr), szFileName );
|
|
|
|
int i=0;
|
|
while( pathstr[i] != NULL )
|
|
{
|
|
i++;
|
|
}
|
|
while( i > 0 && pathstr[i] != '\\' )
|
|
{
|
|
i--;
|
|
}
|
|
pathstr[i] = NULL;
|
|
|
|
// Now write the string to the registry
|
|
RegSetValueEx( hkResult, _T("TTSFiles"), NULL, REG_EXPAND_SZ, (PBYTE)pathstr, (DWORD)_tcslen(pathstr)+1 );
|
|
|
|
RegCloseKey( hkResult );
|
|
|
|
return bRetVal;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
BOOL CTTSApp::CallSaveFileDialog( __in LPTSTR szFileName, LPCTSTR szFilter )
|
|
/////////////////////////////////////////////////////////////////
|
|
//
|
|
// Display the save dialog box to save the wav file
|
|
{
|
|
OPENFILENAME ofn;
|
|
BOOL bRetVal = TRUE;
|
|
LONG lRetVal;
|
|
HKEY hkResult;
|
|
TCHAR szPath[256] = _T("");
|
|
DWORD size = 256;
|
|
|
|
// Open the last directory used by this app (stored in registry)
|
|
lRetVal = RegCreateKeyEx( HKEY_CLASSES_ROOT, _T("PathTTSDataFiles"), 0, NULL, 0,
|
|
KEY_ALL_ACCESS, NULL, &hkResult, NULL );
|
|
|
|
if( lRetVal == ERROR_SUCCESS )
|
|
{
|
|
RegQueryValueEx( hkResult, _T("TTSFiles"), NULL, NULL, (PBYTE)szPath, &size );
|
|
|
|
RegCloseKey( hkResult );
|
|
}
|
|
|
|
size_t ofnsize = (BYTE*)&ofn.lpTemplateName + sizeof(ofn.lpTemplateName) - (BYTE*)&ofn;
|
|
ZeroMemory( &ofn, ofnsize);
|
|
|
|
ofn.lStructSize = (DWORD)ofnsize;
|
|
ofn.hwndOwner = m_hWnd;
|
|
ofn.lpstrFilter = szFilter;
|
|
ofn.lpstrCustomFilter = NULL;
|
|
ofn.nFilterIndex = 1;
|
|
ofn.lpstrInitialDir = szPath;
|
|
ofn.lpstrFile = szFileName;
|
|
ofn.nMaxFile = 256;
|
|
ofn.lpstrTitle = NULL;
|
|
ofn.lpstrFileTitle = NULL;
|
|
ofn.lpstrDefExt = _T("wav");
|
|
ofn.Flags = OFN_OVERWRITEPROMPT;
|
|
|
|
// Pop the dialog
|
|
bRetVal = GetSaveFileName( &ofn );
|
|
|
|
// Write the directory path you're in to the registry
|
|
TCHAR pathstr[256] = _T("");
|
|
_tcscpy_s( pathstr, _countof(pathstr), szFileName );
|
|
|
|
if ( ofn.Flags & OFN_EXTENSIONDIFFERENT )
|
|
{
|
|
_tcscat_s( pathstr, _countof(pathstr), _T(".wav") );
|
|
}
|
|
|
|
int i=0;
|
|
while( pathstr[i] != NULL )
|
|
{
|
|
i++;
|
|
}
|
|
while( i > 0 && pathstr[i] != '\\' )
|
|
{
|
|
i--;
|
|
}
|
|
pathstr[i] = NULL;
|
|
|
|
// Now write the string to the registry
|
|
lRetVal = RegCreateKeyEx( HKEY_CLASSES_ROOT, _T("PathTTSDataFiles"), 0, NULL, 0,
|
|
KEY_ALL_ACCESS, NULL, &hkResult, NULL );
|
|
|
|
if( lRetVal == ERROR_SUCCESS )
|
|
{
|
|
RegSetValueEx( hkResult, _T("TTSFiles"), NULL, REG_EXPAND_SZ, (PBYTE)pathstr, (DWORD)_tcslen(pathstr)+1 );
|
|
|
|
RegCloseKey( hkResult );
|
|
}
|
|
|
|
return bRetVal;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////
|
|
HRESULT CTTSApp::ReadTheFile( LPCTSTR szFileName, BOOL* bIsUnicode, __deref_out WCHAR** ppszwBuff )
|
|
/////////////////////////////////////////////////////
|
|
//
|
|
// This file opens and reads the contents of a file. It
|
|
// returns a pointer to the string.
|
|
// Warning, this function allocates memory for the string on
|
|
// the heap so the caller must free it with 'delete'.
|
|
//
|
|
{
|
|
// Open up the file and copy it's contents into a buffer to return
|
|
HRESULT hr = 0;
|
|
HANDLE hFile;
|
|
DWORD dwSize = 0;
|
|
DWORD dwBytesRead = 0;
|
|
|
|
// First delete any memory previously allocated by this function
|
|
if( m_pszwFileText )
|
|
{
|
|
delete m_pszwFileText;
|
|
}
|
|
|
|
hFile = CreateFile( szFileName, GENERIC_READ,
|
|
FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY, NULL );
|
|
if( hFile == INVALID_HANDLE_VALUE )
|
|
{
|
|
*ppszwBuff = NULL;
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
dwSize = GetFileSize( hFile, NULL );
|
|
if( dwSize == INVALID_FILE_SIZE )
|
|
{
|
|
*ppszwBuff = NULL;
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
}
|
|
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
// Read the file contents into a wide buffer and then determine
|
|
// if it's a unicode or ascii file
|
|
WCHAR Signature = 0;
|
|
|
|
if( ReadFile( hFile, &Signature, 2, &dwBytesRead, NULL ) )
|
|
{
|
|
// Check to see if its a unicode file by looking at the signature of the first character.
|
|
if( 0xFEFF == Signature )
|
|
{
|
|
*ppszwBuff = new WCHAR [dwSize/2];
|
|
|
|
*bIsUnicode = TRUE;
|
|
if( ReadFile( hFile, *ppszwBuff, dwSize-2, &dwBytesRead, NULL ) )
|
|
{
|
|
(*ppszwBuff)[dwSize/2-1] = NULL;
|
|
}
|
|
else
|
|
{
|
|
delete[] *ppszwBuff;
|
|
*ppszwBuff = NULL;
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
}
|
|
else // MBCS source
|
|
{
|
|
char* pszABuff = new char [dwSize+1];
|
|
|
|
*bIsUnicode = FALSE;
|
|
SetFilePointer( hFile, NULL, NULL, FILE_BEGIN );
|
|
if( ReadFile( hFile, pszABuff, dwSize, &dwBytesRead, NULL ) )
|
|
{
|
|
pszABuff[dwSize] = NULL;
|
|
*ppszwBuff = new WCHAR [dwSize+1];
|
|
::MultiByteToWideChar( CP_ACP, 0, pszABuff, -1, *ppszwBuff, dwSize + 1 );
|
|
}
|
|
else
|
|
{
|
|
*ppszwBuff = NULL;
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
delete[] pszABuff;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*ppszwBuff = NULL;
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
CloseHandle( hFile );
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
void CTTSApp::UpdateEditCtlW( LPCWSTR pszwText )
|
|
/////////////////////////////////////////////////////////////////////////
|
|
{
|
|
CComPtr<IRichEditOle> cpRichEdit;
|
|
CComPtr<ITextDocument> cpTextDocument;
|
|
ITextServices* pTextServices=NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
// Use rich edit control interface pointers to update text
|
|
if( SendDlgItemMessage( m_hWnd, IDE_EDITBOX, EM_GETOLEINTERFACE, 0, (LPARAM)(LPVOID FAR *)&cpRichEdit ) )
|
|
{
|
|
hr = cpRichEdit.QueryInterface( &cpTextDocument );
|
|
|
|
if( SUCCEEDED( hr ) )
|
|
{
|
|
hr = cpTextDocument->QueryInterface( IID_ITextServices, (void**)&pTextServices );
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
BSTR bstr = SysAllocString( pszwText );
|
|
|
|
hr = pTextServices->TxSetText( bstr );
|
|
|
|
pTextServices->Release();
|
|
|
|
SysFreeString( bstr );
|
|
}
|
|
}
|
|
|
|
// Add text the old fashon way by converting it to ansi. Note information
|
|
// loss will occur because of the WC2MB conversion.
|
|
if( !cpRichEdit || FAILED( hr ) )
|
|
{
|
|
CW2CT pszFileText(pszwText);
|
|
SetDlgItemText( m_hWnd, IDE_EDITBOX, pszFileText );
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
void CTTSApp::EnableSpeakButtons( BOOL fEnable )
|
|
/////////////////////////////////////////////////////////////////////////
|
|
{
|
|
::EnableWindow( ::GetDlgItem( m_hWnd, IDB_SPEAK ), fEnable );
|
|
::EnableWindow( ::GetDlgItem( m_hWnd, IDB_PAUSE ), fEnable );
|
|
::EnableWindow( ::GetDlgItem( m_hWnd, IDB_STOP ), fEnable );
|
|
::EnableWindow( ::GetDlgItem( m_hWnd, IDB_SKIP ), fEnable );
|
|
::EnableWindow( ::GetDlgItem( m_hWnd, IDB_SPEAKWAV ), fEnable );
|
|
::EnableWindow( ::GetDlgItem( m_hWnd, IDC_SAVETOWAV ), fEnable );
|
|
}
|
|
|
|
/////////////////////////////////////////////////////
|
|
inline void TTSAppStatusMessage( HWND hWnd, LPCTSTR szMessage )
|
|
/////////////////////////////////////////////////////
|
|
//
|
|
// This function prints debugging messages to the debug edit control
|
|
//
|
|
{
|
|
static TCHAR szDebugText[MAX_SIZE]=_T("");
|
|
static int i = 0;
|
|
|
|
// Clear out the buffer after 100 lines of text have been written
|
|
// to the debug window since it can only hold 4096 characters.
|
|
if( i == 100 )
|
|
{
|
|
_tcscpy_s( szDebugText, _countof(szDebugText), _T("") );
|
|
i = 0;
|
|
}
|
|
// Attach the new message to the ongoing list of messages
|
|
_tcscat_s( szDebugText, _countof(szDebugText), szMessage );
|
|
SetDlgItemText( hWnd, IDC_DEBUG, szDebugText );
|
|
|
|
SendDlgItemMessage( hWnd, IDC_DEBUG, EM_LINESCROLL, 0, i );
|
|
|
|
i++;
|
|
|
|
return;
|
|
}
|
|
|
|
/*****************************************************************************************
|
|
* About() *
|
|
*---------*
|
|
* Description:
|
|
* Message handler for the "About" box.
|
|
******************************************************************************************/
|
|
LRESULT CALLBACK About( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
|
|
{
|
|
switch( message )
|
|
{
|
|
case WM_COMMAND:
|
|
{
|
|
WORD wId = LOWORD(wParam);
|
|
|
|
switch( wId )
|
|
{
|
|
case IDOK:
|
|
case IDCANCEL:
|
|
EndDialog( hDlg, LOWORD(wParam) );
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
} /* About */
|