// 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 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; iSetNotifyWindowMessage( 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 cpWavStream; CComPtr 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 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 cpRichEdit; CComPtr 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 */