I use the following code for Text-To-Speech application controls for blind persons in C++ Builder (most likely similar example can be used in Delphi). Main form has KeyPreview property checked to enable key F11 preview to start speaking active (focused) control. The code as it is works but there are some problems. This example is in C++ Builder code but from what I've found, Delphi suffers from same problem and the solution I found is the same. If you have Delphi solution, feel free to post it, it is similar anyway.
#include <sapi.h>
#include <WTypes.h>
//---------------------------------------------------------------------------
// Speak text string (synchronous function)
//---------------------------------------------------------------------------
bool SpeakText(UnicodeString Text)
{
ISpVoice* pVoice = NULL;
if (FAILED(::CoInitialize(NULL))) return false;
Word Saved8087CW = Default8087CW; // Disable floating point division by zero exception caused by Speak
Set8087CW(0x133f);
HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);
if (SUCCEEDED(hr))
{
//pVoice->SpeakCompleteEvent()
//pVoice->SetSyncSpeakTimeout(1000);
hr = pVoice->Speak(WideString(Text).c_bstr(), SPF_DEFAULT, NULL);
pVoice->Release();
pVoice = NULL;
}
Set8087CW(Saved8087CW);
::CoUninitialize();
return true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift)
{
UnicodeString Speaker;
if (Key == VK_F11)
{
if (Screen->ActiveControl->InheritsFrom(__classid(TButton))) { Speaker += "Button, " + static_cast<TButton*>(Screen->ActiveControl)->Caption + "."; }
else if (Screen->ActiveControl->InheritsFrom(__classid(TEdit))) { Speaker += "Edit box, " + static_cast<TEdit*>(Screen->ActiveControl)->Text + "."; }
}
if (Speaker != "") SpeakText(Speaker);
}
//---------------------------------------------------------------------------
Problems:
pVoice->Speak causes Floating point division by zero if I don't override the exception using the
Set8087CWfunction. This happens only on Windows 7 (possibly Vista and Windows 8 too) but not on Windows XP in the same program (compiled exe). Is there a solution without usingSet8087CW? Removing these lines will cause the problem and exception. I have BCB2010.Function is synchronous and won't shut up or return control to program until it finishes reading text. This is a problem for longer text. It also blocks program events. Is there a way to make it asynchronous or introduce an event to periodically check for F11 key status and if F11 is pressed again it stops reading and uninitializes object? For example poll every 300 ms (or after each word etc.) for key-press F11 and if pressed, stop speaking? Or run it threaded?
Does SAPI has memory leaks as some write on various sites?
Can above code use
OleCheckinstead ofCoCreateInstanceandCoUninitialize?
UPDATE for those looking for solution as suggested by Remy Lebeau:
SavedCW = Get8087CW();
Set8087CW(SavedCW | 0x4);
hr = pVoice->Speak(WideString(Text).c_bstr(), SPF_DEFAULT | SPF_ASYNC, NULL);
pVoice->WaitUntilDone(-1); // Waits until text is done... if F11 is pressed simply go out of scope and speech will stop
Set8087CW(SavedCW);
Also found detailed example in CodeRage 4 session: http://cc.embarcadero.com/item/27264