private void SpeakWithMicrosoftSpeechLibrary(string textToSpeak, Action onComplete, int?volume, int?rate, string voice) { var voiceToUse = voice ?? Settings.Default.SpeechVoice; if (!string.IsNullOrWhiteSpace(voiceToUse)) { if (!useLegacyMicrosoftSpeechForVoices.Contains(voiceToUse)) { try { speechSynthesiser.SelectVoice(voiceToUse); speechSynthesiser.Rate = rate ?? Settings.Default.SpeechRate; speechSynthesiser.Volume = volume ?? Settings.Default.SpeechVolume; } catch //(Exception exception) { //Commenting out the raising of an error notification for now //var customException = new ApplicationException(string.Format(Resources.UNABLE_TO_SET_VOICE_WARNING, // voiceToUse, voice == null ? Resources.VOICE_COMES_FROM_SETTINGS : null), exception); //PublishError(this, customException); Log.Warn($"Unable to speak using SpeechSynthesizer and voice '{voiceToUse}'. Switching to legacy speech mode and trying again..."); useLegacyMicrosoftSpeechForVoices.Add(voiceToUse); } } if (useLegacyMicrosoftSpeechForVoices.Contains(voiceToUse)) { Log.Info("Attempting speech using legacy mode."); try { if (legacySpeechSynthesiser == null) { //Lazy instantiate legacy speech synthesiser legacySpeechSynthesiser = new SpVoice(); } var availableVoices = legacySpeechSynthesiser.GetVoices(string.Empty, string.Empty); if (legacyMicrosoftSpeechVoiceToTokenIndexLookup.ContainsKey(voiceToUse)) { int voiceIndex = legacyMicrosoftSpeechVoiceToTokenIndexLookup[voiceToUse]; Log.InfoFormat($"{voiceToUse} voice token exists at index {voiceIndex}. Setting voice on legacy speech synthesiser."); legacySpeechSynthesiser.Voice = availableVoices.Item(voiceIndex); Log.Info("Voice token set."); } else { for (int voiceIndex = 0; voiceIndex < availableVoices.Count; voiceIndex++) { var voiceToken = availableVoices.Item(voiceIndex); if (voiceToken.GetDescription() == voiceToUse) { Log.InfoFormat($"{voiceToUse} voice token found at index {voiceIndex}. Setting voice on legacy speech synthesiser."); legacyMicrosoftSpeechVoiceToTokenIndexLookup.Add(voiceToUse, voiceIndex); legacySpeechSynthesiser.Voice = voiceToken; Log.Info("Voice token set."); break; } } } } catch (Exception exception) { var customException = new ApplicationException(string.Format(Resources.UNABLE_TO_SET_VOICE_WARNING, voiceToUse, voice == null ? Resources.VOICE_COMES_FROM_SETTINGS : null), exception); PublishError(this, customException); } } } //Speak if (!useLegacyMicrosoftSpeechForVoices.Contains(voiceToUse)) { onSpeakCompleted = (sender, args) => { lock (speakCompletedLock) { if (onSpeakCompleted != null) { speechSynthesiser.SpeakCompleted -= onSpeakCompleted; onSpeakCompleted = null; } if (onComplete != null) { onComplete(); } } }; speechSynthesiser.SpeakCompleted += onSpeakCompleted; speechSynthesiser.SpeakAsync(textToSpeak); } else { //Legacy speech mode if (legacySpeechSynthesiser != null) { legacySpeechSynthesiser.Speak(textToSpeak, SpeechVoiceSpeakFlags.SVSFIsNotXML | SpeechVoiceSpeakFlags.SVSFlagsAsync); var speechHandle = legacySpeechSynthesiser.SpeakCompleteEvent(); var speechHandlePtr = new IntPtr(speechHandle); if (speechHandlePtr != IntPtr.Zero) { var autoResetEvent = new AutoResetEvent(false) { SafeWaitHandle = new SafeWaitHandle(speechHandlePtr, false) }; var uiThreadDispatcher = Dispatcher.CurrentDispatcher; legacySpeakCompleted = (state, timedOut) => { if (onComplete != null) { uiThreadDispatcher.Invoke(onComplete); } autoResetEvent.Dispose(); legacySpeakCompleted = null; }; ThreadPool.RegisterWaitForSingleObject(autoResetEvent, legacySpeakCompleted, null, 30000, true); } } } }