2

First of all, C++ is not my language (I'm a java programmer).

I've seen examples of Microsoft SAPI 5.1, and I need a way to print all the available voices in a machine. I've seen the Object token of SAPI, and I'm kinda confuse of how CComPtr is use to iterate through. Can anyone help me with this. Additionally, how can I pick a specific voice instead of using the default.

Based from the examples, you can get the default voice by invoking this codes:

ISpVoice * pVoice = NULL;

if (FAILED(::CoInitialize(NULL)))
    return FALSE;

HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice);

So in short: How can I list all the voices available and how can I choose the voice?

//PS: I don't need to put it in a combo box (just like all examples that I've seen), I'm implementing this for a JNI dll library so an example of iteration is much preferred.

Marl
  • 1,492
  • 2
  • 22
  • 37

2 Answers2

4

First off, CComPtr is a smart pointer abstraction over COM interfaces, not an enumerator.

To list all the voices, you need to enumerate the tokens in the SPCAT_VOICES category.

HRESULT hr = S_OK;
CComPtr<ISpObjectTokenCategory> cpSpCategory = NULL; 
if (SUCCEEDED(hr = SpGetCategoryFromId(SPCAT_VOICES, &cpSpCategory))) 
{ 
    CComPtr<IEnumSpObjectTokens> cpSpEnumTokens; 
    if (SUCCEEDED(hr = cpSpCategory->EnumTokens(NULL, NULL, &cpSpEnumTokens))) 
   { 
        CComPtr<ISpObjectToken> pSpTok;
        while (SUCCEEDED(hr = cpSpEnumTokens->Next(1,&pSpTok, NULL)))) 
        {
            // do something with the token here; for example, set the voice
            pVoice->SetVoice(pSpTok,FALSE); 
            // NOTE:  IEnumSpObjectTokens::Next will *overwrite* the pointer; must manually release
            pSpTok.Release(); 
        }
   } 
} 

If you want to pick a specific voice, you need to specify an attribute that's unique to that voice. EnumTokens allows you to specify required and optional attributes. The attributes are actually defined by the creator of the voice, but some common attributes are

  • Name (e.g., "Microsoft Anna")
  • Language (specified by a numeric locale ID)
  • Gender (male/female)
  • Vendor (name of company that created the voice)

So to specify "Microsoft Anna" instead of any voice, you would use

if (SUCCEEDED(hr = cpSpCategory->EnumTokens(L"Name=Microsoft Anna", NULL, &cpSpEnumTokens))) 

instead of

if (SUCCEEDED(hr = cpSpCategory->EnumTokens(NULL, NULL, &cpSpEnumTokens))) 
Eric Brown
  • 13,774
  • 7
  • 30
  • 71
  • Thanks for the solution :) Btw, the inner while loop runs infinitely. I think we can use the GetCount method to keep count of no tokens, so that we know when to stop. I am sure, you would be aware of other methods. – Yash Jun 02 '14 at 13:56
  • the call to get language returns an MS LangID, it needs to be converted to a BCP 47 language tag (eg. en-GB). How do we do it ? – Yash Aug 15 '14 at 13:16
  • 1
    @YashGirdhar Try [LCIDToLocaleName](http://msdn.microsoft.com/en-us/library/windows/desktop/dd318698(v=vs.85).aspx). This would be a good question, incidentally.... – Eric Brown Aug 15 '14 at 14:16
  • Thanks for the link .But I am not able to achieve it. Say, it returns "809", now how do I convert it into LCID first, as LCIDHex for English is "0809", and LCIDec is "2057". – Yash Aug 15 '14 at 15:24
  • You need to open a separate question for this. – Eric Brown Aug 15 '14 at 18:55
  • http://stackoverflow.com/questions/25337602/how-to-convert-sapis-ms-lang-id-to-bcp-47-language-tag – Yash Aug 16 '14 at 06:36
0

The enum method does work but is a little clunky. The best way is to open the registry and go through the keys and their attributes, you can find more information here:

msdn: SAPI Tokens, Categories and the Registry

Again that's a bit of work, so I've created an interface while doing something else and it may help you or somebody else:

Sapi interface for enumerating and searching voice tokens

clu
  • 1
  • 2