0

I'm developping a Text-To-Speech application using the Microsoft sapi library. I implemented the speaking function and discovered that the accented characters (à,á,â,ä,é,è,ê,í,ì,î,ó,ò,ô,ö,ù,ú,û,ü) are not spoken. Here is my code:

int ttsSpeak( const char* text ) //Text to Speech speaking function
{
  if( SUCCEEDED(hr) )
  {
    hr = SpEnumTokens( SPCAT_VOICES, NULL, NULL, &cpEnum );

    cpEnum->Item( saveVoice, &cpVoiceToken );
    cpVoice->SetVoice( cpVoiceToken ); //Initialization of the voice

    string str( text );
    cout << str;
    std::wstring stemp = std::wstring( str.begin(), str.end() );
    LPCWSTR sw = ( LPCWSTR )stemp.c_str(); //variable allowing to speak my entered text

    printf( "Text To Speech processing\n" );
    hr = cpVoice->Speak( sw, SPF_DEFAULT, NULL ); //speak my text

    saveText = text;

    cpEnum.Release();
    cpVoiceToken.Release();
  }
  else
  {
    printf( "Could not speak entered text\n" );
  }

  return true;
}

I debugged my app and found out that the variable str gets the accented characters. However, I create a wstring variable called stemp where my string is converted, and here the accented character is replaced with a empty square. Then, a LPCWSTR variable (Long Pointer to Constant Wide String) is created in order to speak the entered text. Below a picture of my variables values.

Variable values

Maybe there is something wrong in my code, but what can I do to ensure that the accented characters are spoken out?

georges619
  • 288
  • 1
  • 6
  • 18
  • 2
    `std::wstring( str.begin(), str.end() );` will work correctly only if source string contains only ASCII symbols. In general case you will need to perform encoding conversion. – user7860670 Mar 20 '19 at 15:08
  • The most likely culprit is the character encoding. Simply widening each character will not produce a correct string. – molbdnilo Mar 20 '19 at 15:10
  • I need to perform encoding conversion between `string str( text );` and `std::wstring( str.begin(), str.end() );` right? And how can I do this? – georges619 Mar 20 '19 at 15:11
  • 1
    there are a lot of duplicates on that: [C++ Convert string (or char*) to wstring (or wchar_t*)](https://stackoverflow.com/q/2573834/995714), [How to convert string to wstring in C++](https://stackoverflow.com/q/25485268/995714), [converting narrow string to wide string](https://stackoverflow.com/q/6691555/995714), [How to convert CString and ::std::string ::std::wstring to each other?](https://stackoverflow.com/q/258050/995714)... – phuclv Mar 20 '19 at 16:13
  • Even if you convert the string correctly, you might need to choose the right normalization form, since accented latin letters can typically be represented a couple different ways in Unicode. The Microsoft docs don't mention which form the API expects, but a good guess is NFC. – Adrian McCarthy Mar 20 '19 at 18:27
  • @AdrianMcCarthy: Unicode basically says that the different representations are equivalent. I.e. `á==á` regardless of representation. – MSalters Mar 21 '19 at 09:38
  • @MSalters: True, but that doesn't mean the implementation of this API does that. I did qualify my comment with "might." – Adrian McCarthy Mar 21 '19 at 16:23

2 Answers2

2

You can't simply copy a single-byte or multi-byte character string (char, std::string) to a wide character string (wchar_t, std::wstring). You need to do proper conversion between encodings or character sets.

You have to determine the correct encodings used for both strings. On Windows, std::string data is usually in a local encoding, such as Windows-1252 and std::wstring data is in UTF-16.

On Windows, you can use MultiByteToWideChar for the conversion.

Alternatively, you can use standard functions such as mbstowcs or std::mbtowc.

rveerd
  • 3,620
  • 1
  • 14
  • 30
  • 1
    Or, you could simply change `ttsSpeak()` to take a `const wchar_t*` to begin with, and get rid of the `str` and `stemp` local variables altogether, and pass `text` directly to `Speak()`. Then it becomes the responsibility of the caller of `ttsSpeak()` to pass in a valid Unicode string. – Remy Lebeau Mar 20 '19 at 17:11
  • @RemyLebeau True, but that's more an architecture choice. From the question, it appears the poster doesn't even know the concepts of character types and encodings and that's the first thing to get right. – rveerd Mar 21 '19 at 07:31
  • @rveerd, the **MultiByteToWideChar** worked. However, I found a shorter way to implement it, but I will accept your answer – georges619 Mar 21 '19 at 08:07
0

I implemented the MultiByteToWideChar suggested by @rveerd. Here is the code:

int ttsSpeak( const char* text ) //Text to Speech speaking function
{
  if( SUCCEEDED(hr) )
  {
    hr = SpEnumTokens( SPCAT_VOICES, NULL, NULL, &cpEnum );

    cpEnum->Item( saveVoice, &cpVoiceToken );
    cpVoice->SetVoice( cpVoiceToken ); //Initialization of the voice

    //processing conversion
    int wchars_num = MultiByteToWideChar( CP_ACP, 0, text, -1, NULL, 0 ); 
    wchar_t* wstr = new wchar_t[ wchars_num ];
    MultiByteToWideChar( CP_ACP, 0, text, -1, wstr, wchars_num );

    printf( "Text To Speech processing\n" );
    hr = cpVoice->Speak( wstr, SPF_DEFAULT, NULL ); //speak my text

    saveText = text;

    cpEnum.Release();
    cpVoiceToken.Release();
    delete new wchar_t[wchars_num];
  }
  else
  {
    printf( "Could not speak entered text\n" );
  }

  return true;
}

I also found a shorter way to convert it. Just replace the MultiByteToWideChar code with following:

CA2W pszWide( str.c_str(), CP_ACP);
hr = cpVoice->Speak( pszWide, SPF_DEFAULT, NULL );

Edit: I replaced CP_UTF7 because it is rarely used. CP_UTF8 is prefered. However, it didn't worked for me, but I found out that CP_ACP works for me. For more information look at the link @rveerd posted

georges619
  • 288
  • 1
  • 6
  • 18
  • The chances of your input being UTF-7 are incredibly small. UTF-8 is probably a million times more common, and that is not exaggerating. – MSalters Mar 21 '19 at 09:39
  • @MSalters I tried with UTF-8, but I had the same error as explained in the context. This time, the **ç** was replaced with a **? in a black lozenge** – georges619 Mar 21 '19 at 09:45
  • Your input might also be in CP-1252 or a host of other common encodings. UTF-7 is really rare, if not outright theoretical. – MSalters Mar 21 '19 at 09:47
  • @MSalters you're probably right. I rechecked the documentation about [MultiByteToWideChar](https://learn.microsoft.com/en-us/windows/desktop/api/stringapiset/nf-stringapiset-multibytetowidechar) and its written that UTF-8 is preferred. However, UTF-8 doesn't work for me, and I replaced UTF-7 with **CP_ACP**. Is this better ? – georges619 Mar 21 '19 at 10:03
  • 2
    @georges619 Yes, CP_ACP is usually what you want to use if the data comes from a Windows environment. – rveerd Mar 21 '19 at 11:59
  • 1
    In the `MultiByteToWideChar()` example, the conversion from `char*` to `string` is unnecessary, just pass the `char*` directly to `MultiByteToWideChar()`. And don't forget to `delete[]` the `wchar_t*` you allocated with `new[]`. – Remy Lebeau Mar 21 '19 at 15:00
  • @RemyLebeau, I edited the code as you mentionned. Thanks for the advice :) – georges619 Mar 21 '19 at 15:44