Code:
/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Speech / Src / Internal / Synthesis / VoiceSynthesis.cs / 1 / VoiceSynthesis.cs
//------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // // // Description: // This module is the main implementation file for the CSpVoice class and // it's associated event management logic. This is the main SAPI5 COM object // for all of TTS. // // History: // 2/1/2005 [....] Created from the Sapi Managed code //----------------------------------------------------------------- using System; using System.IO; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; using System.Security; using System.Speech.AudioFormat; using System.Speech.Internal; using System.Speech.Internal.ObjectTokens; using System.Speech.Synthesis; using System.Speech.Synthesis.TtsEngine; using System.Text; using System.Threading; #if (SPEECHSERVER || PROMPT_ENGINE) && !SERVERTESTDLL using System.Security.Cryptography; #endif #pragma warning disable 1634, 1691 // Allows suppression of certain PreSharp messages. #pragma warning disable 56502 // Empty catch statements namespace System.Speech.Internal.Synthesis { internal sealed class VoiceSynthesis : IDisposable { //******************************************************************* // // Constructors // //******************************************************************* #region Constructors internal VoiceSynthesis (WeakReference speechSynthesizer) { _asyncWorker = new AsyncSerializedWorker (new WaitCallback (ProcessPostData), null); _asyncWorkerUI = new AsyncSerializedWorker (null, AsyncOperationManager.CreateOperation (null)); // Setup the event dispatcher for state changed events _eventStateChanged = new WaitCallback (OnStateChanged); // Setup the event dispatcher for all other events _signalWorkerCallback = new WaitCallback (SignalWorkerThread); // _speechSyntesizer = speechSynthesizer; // Initialize the engine site; _resourceLoader = new ResourceLoader (); _site = new EngineSite (_resourceLoader); // No pending work and speaking is done _evtPendingSpeak.Reset (); // Create the default audio device (speaker) #if SPEECHSERVER && !SERVERTESTDLL _waveOut = new AudioFileOut (Stream.Null, new SpeechAudioFormatInfo (EncodingFormat.ALaw, 8000, 8, 1, 8000, 1, null), false, _asyncWorker); #else _waveOut = new AudioDeviceOut (SAPICategories.DefaultDeviceOut (), _asyncWorker); #endif // Build the installed voice collection on first run if (_allVoices == null) { _allVoices = BuildInstalledVoices (this); // If no voice are installed, then bail out. if (_allVoices.Count == 0) { _allVoices = null; throw new PlatformNotSupportedException (SR.Get (SRID.SynthesizerVoiceFailed)); } } // Create a dynamic list of installed voices from the list of all available voices. _installedVoices = new List(_allVoices.Count); foreach (InstalledVoice installedVoice in _allVoices) { _installedVoices.Add (new InstalledVoice (this, installedVoice.VoiceInfo)); } // Get the default rate _site.VoiceRate = _defaultRate = (int) GetDefaultRate (); // Start the worker thread _workerThread = new Thread (new ThreadStart (ThreadProc)); _workerThread.IsBackground = true; _workerThread.Start (); // Default TTS engines events to be notified SetInterest (_ttsEvents); } ~VoiceSynthesis () { Dispose (false); } /// /// /// public void Dispose () { Dispose (true); GC.SuppressFinalize (this); } #endregion //******************************************************************** // // Internal Methods // //******************************************************************* #region Internal Methods #region SpeechSynthesis 'public' API implementation ////// TODOC /// /// ///internal void Speak (Prompt prompt) { bool done = false; EventHandler eventHandler = delegate (object sender, StateChangedEventArgs args) { if (prompt.IsCompleted && args.State == SynthesizerState.Ready) { done = true; _workerWaitHandle.Set (); } }; try { _stateChanged += eventHandler; _asyncWorkerUI.AsyncMode = false; _asyncWorkerUI.WorkItemPending += _signalWorkerCallback; // SpeakAsync the prompt QueuePrompt (prompt); while (!done && !_isDisposed) { _workerWaitHandle.WaitOne (); _asyncWorkerUI.ConsumeQueue (); } // Throw if an exception occured if (prompt._exception != null) { throw prompt._exception; } } finally { _asyncWorkerUI.AsyncMode = true; _asyncWorkerUI.WorkItemPending -= _signalWorkerCallback; _stateChanged -= eventHandler; } } /// /// TODOC /// /// ///internal void SpeakAsync (Prompt prompt) { QueuePrompt (prompt); } #region Speech Synthesis events internal void OnSpeakStarted (SpeakStartedEventArgs e) { if (_speakStarted != null) { _asyncWorkerUI.PostOperation (_speakStarted, _speechSyntesizer.Target, e); } } internal void FireSpeakCompleted (object sender, SpeakCompletedEventArgs e) { if (_speakCompleted != null && !e.Prompt._syncSpeak) { _speakCompleted (sender, e); } e.Prompt.Synthesizer = null; } internal void OnSpeakCompleted (SpeakCompletedEventArgs e) { e.Prompt.IsCompleted = true; _asyncWorkerUI.PostOperation (new EventHandler (FireSpeakCompleted), _speechSyntesizer.Target, e); } internal void OnSpeakProgress (SpeakProgressEventArgs e) { if (_speakProgress != null) { string text = string.Empty; if (e.Prompt._media == SynthesisMediaType.Ssml) { int length = e.CharacterCount; text = RemoveEscapeString (e.Prompt._text, e.CharacterPosition, length, out length); e.CharacterCount = length; } else { text = e.Prompt._text.Substring (e.CharacterPosition, e.CharacterCount); } e.Text = text; _asyncWorkerUI.PostOperation (_speakProgress, _speechSyntesizer.Target, e); } } private string RemoveEscapeString (string text, int start, int length, out int newLength) { newLength = length; // Find the pos '>' from the start position and so sustitution from this point on int startInXml = text.LastIndexOf ('>', start); System.Diagnostics.Debug.Assert (startInXml >= 0); // Check for special character strings "%gt;", etc... and convert them to "<" etc... int curPos = startInXml; StringBuilder sb = new StringBuilder (text.Substring (0, curPos)); do { // Look for one of the Xml escape string int iEscapeString = -1; int pos = int.MaxValue; for (int i = 0; i < xmlEscapeStrings.Length; i++) { int idx; if ((idx = text.IndexOf (xmlEscapeStrings [i], curPos, StringComparison.Ordinal)) >= 0) { if (pos > idx) { pos = idx; iEscapeString = i; } } } if (iEscapeString < 0) { // If no special string have been found then the current position is the end of the string. pos = text.Length; } else if (pos >= startInXml) { // For the character that is replacing the escape sequence. newLength += xmlEscapeStrings [iEscapeString].Length - 1; } else { // Found an escape sequence but it is it before the current text fragment. pos += xmlEscapeStrings [iEscapeString].Length; iEscapeString = -1; } // add the new string int len = pos - curPos; sb.Append (text.Substring (curPos, len)); if (iEscapeString >= 0) { sb.Append (xmlEscapeChars [iEscapeString]); int lenEscape = xmlEscapeStrings [iEscapeString].Length; pos += lenEscape; } curPos = pos; } while (start + length > sb.Length); return sb.ToString ().Substring (start, length); } internal void OnBookmarkReached (BookmarkReachedEventArgs e) { if (_bookmarkReached != null) { _asyncWorkerUI.PostOperation (_bookmarkReached, _speechSyntesizer.Target, e); } } internal void OnVoiceChange (VoiceChangeEventArgs e) { if (_voiceChange != null) { _asyncWorkerUI.PostOperation (_voiceChange, _speechSyntesizer.Target, e); } } #if !SPEECHSERVER internal void OnPhonemeReached (PhonemeReachedEventArgs e) { if (_phonemeReached != null) { _asyncWorkerUI.PostOperation (_phonemeReached, _speechSyntesizer.Target, e); } } private void OnVisemeReached (VisemeReachedEventArgs e) { if (_visemeReached != null) { _asyncWorkerUI.PostOperation (_visemeReached, _speechSyntesizer.Target, e); } } #else internal void OnProprietaryEngineEvent (ProprietaryEngineEventArgs e) { if (_proprietaryEngineEvent != null) { _asyncWorkerUI.PostOperation(_proprietaryEngineEvent, _speechSyntesizer.Target, e); } } #endif private void OnStateChanged (object o) { // For all other events the lock is done in the dispatch method lock (_thisObjectLock) { StateChangedEventArgs e = (StateChangedEventArgs) o; if (_stateChanged != null) { _asyncWorkerUI.PostOperation (_stateChanged, _speechSyntesizer.Target, e); } } } internal void AddEvent (TtsEventId ttsEvent, ref EventHandler internalEventHandler, EventHandler eventHandler) where T : PromptEventArgs { lock (_thisObjectLock) { Helpers.ThrowIfNull (eventHandler, "eventHandler"); // could through if unsuccessful - delay the SetEventInterest bool fSetSapiInterest = internalEventHandler == null; internalEventHandler += eventHandler; if (fSetSapiInterest) { _ttsEvents |= (1 << (int) ttsEvent); SetInterest (_ttsEvents); } } } internal void RemoveEvent (TtsEventId ttsEvent, ref EventHandler internalEventHandler, EventHandler eventHandler) where T : EventArgs { lock (_thisObjectLock) { Helpers.ThrowIfNull (eventHandler, "eventHandler"); // could through if unsuccessful - delay the SetEventInterest internalEventHandler -= eventHandler; if (internalEventHandler == null) { _ttsEvents &= ~(1 << (int) ttsEvent); SetInterest (_ttsEvents); } } } #endregion #endregion /// /// TODOC /// /// /// /// internal void SetOutput (Stream stream, SpeechAudioFormatInfo formatInfo, bool headerInfo) { lock (_pendingSpeakQueue) { // Output is not supposed to change while speaking. if (State == SynthesizerState.Speaking) { throw new InvalidOperationException (SR.Get (SRID.SynthesizerSetOutputSpeaking)); } if (State == SynthesizerState.Paused) { throw new InvalidOperationException (SR.Get (SRID.SynthesizerSyncSetOutputWhilePaused)); } lock (_processingSpeakLock) { if (stream == null) { #if SPEECHSERVER && !SERVERTESTDLL _waveOut = new AudioFileOut (Stream.Null, null, false, _asyncWorker); #else _waveOut = new AudioDeviceOut (SAPICategories.DefaultDeviceOut (), _asyncWorker); #endif } else { _waveOut = new AudioFileOut (stream, formatInfo, headerInfo, _asyncWorker); } } } } ////// Description: /// This method synchronously purges all data that is currently in the /// rendering pipeline. /// internal void Abort () { //--- Purge all pending speak requests and reset the voice lock (_pendingSpeakQueue) { lock (_site) { if (_currentPrompt != null) { _site.Abort (); _waveOut.Abort (); } } lock (_processingSpeakLock) { Parameters [] parameters = _pendingSpeakQueue.ToArray (); foreach (Parameters parameter in parameters) { ParametersSpeak paramSpeak = parameter._parameter as ParametersSpeak; if (paramSpeak != null) { paramSpeak._prompt._exception = new OperationCanceledException (SR.Get (SRID.PromptAsyncOperationCancelled)); } } // Restart the worker thread _evtPendingSpeak.Set (); } } } ////// Description: /// This method synchronously purges all data that is currently in the /// rendering pipeline. /// internal void Abort (Prompt prompt) { //--- Purge all pending speak requests and reset the voice lock (_pendingSpeakQueue) { bool found = false; foreach (Parameters parameters in _pendingSpeakQueue) { ParametersSpeak paramSpeak = parameters._parameter as ParametersSpeak; if (paramSpeak._prompt == prompt) { paramSpeak._prompt._exception = new OperationCanceledException (SR.Get (SRID.PromptAsyncOperationCancelled)); found = true; break; } } if (!found) { // Not in the list, it could be the current prompt lock (_site) { if (_currentPrompt == prompt) { _site.Abort (); _waveOut.Abort (); } } // Wait for completion lock (_processingSpeakLock) { } } } } ////// Pause the audio /// internal void Pause () { lock (_waveOut) { if (_waveOut != null) { _waveOut.Pause (); } lock (_pendingSpeakQueue) { // The pause arrived after a speak call was initiated but before it started to speak // Simulated a Re if (_pendingSpeakQueue.Count > 0 && State == SynthesizerState.Ready) { OnStateChanged (SynthesizerState.Speaking); } OnStateChanged (SynthesizerState.Paused); } } } ////// Resume the audio /// internal void Resume () { lock (_waveOut) { if (_waveOut != null) { _waveOut.Resume (); } lock (_pendingSpeakQueue) { if (_pendingSpeakQueue.Count > 0 || _currentPrompt != null) { OnStateChanged (SynthesizerState.Speaking); } else { // The state could be set to paused if the Paused happened after the speak happened if (State == SynthesizerState.Paused) { OnStateChanged (SynthesizerState.Speaking); } OnStateChanged (SynthesizerState.Ready); } } } } internal void AddLexicon (Uri uri, string mediaType) { LexiconEntry lexiconEntry = new LexiconEntry (uri, mediaType); lock (_processingSpeakLock) { foreach (LexiconEntry lexicon in _lexicons) { if (lexicon._uri.Equals (uri)) { throw new InvalidOperationException (SR.Get (SRID.DuplicatedEntry)); } } _lexicons.Add (lexiconEntry); } } internal void RemoveLexicon (Uri uri) { lock (_processingSpeakLock) { foreach (LexiconEntry lexicon in _lexicons) { if (lexicon._uri.Equals (uri)) { _lexicons.Remove (lexicon); // Bail out found return; } } throw new InvalidOperationException (SR.Get (SRID.FileNotFound, uri.ToString ())); } } ////// This method is used to create the Engine voice and initialize the culture /// /// /// /// /// /// /// ///internal TTSVoice GetEngine (string name, CultureInfo culture, VoiceGender gender, VoiceAge age, int variant, bool switchContext) { TTSVoice defaultVoice = _currentVoice != null ? _currentVoice : GetVoice (switchContext); return GetEngineWithVoice (defaultVoice, null, name, culture, gender, age, variant, switchContext); } /// /// Returns the voices for a given (or all cultures) /// /// Culture or null for all culture ///internal ReadOnlyCollection GetInstalledVoices (CultureInfo culture) { if (culture == null || culture == CultureInfo.InvariantCulture) { return new ReadOnlyCollection (_installedVoices); } else { Collection voices = new Collection (); // loop all the available voices in the registry // no check if the voice are valid foreach (InstalledVoice voice in _installedVoices) { // Either all voices if culture is if (culture.Equals (voice.VoiceInfo.Culture)) { voices.Add (voice); } } return new ReadOnlyCollection (voices); } } #if SPEECHSERVER || PROMPT_ENGINE internal void LoadDatabase (string localName, string alias) { Helpers.ThrowIfEmptyOrNull (localName, "localName"); ExecuteOnBackgroundThread (Action.LoadDatabase, new ParametersDatabase (localName, alias)); if (_pendingException != null) { throw _pendingException; } } internal void UnloadDatabase (string alias) { Helpers.ThrowIfEmptyOrNull (alias, "alias"); ExecuteOnBackgroundThread (Action.UnloadDatabase, alias); if (_pendingException != null) { throw _pendingException; } } internal void SetResourceLoader (ISpeechResourceLoader resourceLoader) { lock (_processingSpeakLock) { _resourceLoader.SetResourceLoader (resourceLoader); } } #endif #endregion //******************************************************************** // // Internal Propperties // //******************************************************************** #region Internal Propperties /// /// TODOC /// ///internal Prompt Prompt { get { lock (_pendingSpeakQueue) { return _currentPrompt; } } } /// /// TODOC /// ///internal SynthesizerState State { get { return _synthesizerState; } } /// /// TODOC /// ///internal int Rate { set { _site.VoiceRate = _defaultRate = value; } get { return _site.VoiceRate; } } /// /// TODOC /// ///internal int Volume { set { _site.VoiceVolume = value; } get { return _site.VoiceVolume; } } /// /// Set/Get the default voice /// internal TTSVoice Voice { set { lock (_defaultVoiceLock) { if (_currentVoice == _defaultVoice && value == null) { _defaultVoiceInitialized = false; } _currentVoice = value; } } } ////// Set/Get the default voice /// internal TTSVoice CurrentVoice (bool switchContext) { lock (_defaultVoiceLock) { // If no voice defined then get the default voice if (_currentVoice == null) { GetVoice (switchContext); } return _currentVoice; } } #if (SPEECHSERVER || PROMPT_ENGINE) && !SERVERTESTDLL ////// Equivelant to the C runtime function time_t time(time_t*); /// private static TimeSpan Time { get { return (DateTime.UtcNow - time_t_0); } } #endif #endregion //******************************************************************* // // Internal Fields // //******************************************************************** #region Internal Fields // Internal event handlers internal EventHandler_stateChanged; // Internal event handlers internal EventHandler _speakStarted; internal EventHandler _speakCompleted; internal EventHandler _speakProgress; internal EventHandler _bookmarkReached; internal EventHandler _voiceChange; #if !SPEECHSERVER internal EventHandler _phonemeReached; internal EventHandler _visemeReached; #else internal EventHandler _proprietaryEngineEvent; #endif #if SPEECHSERVER || PROMPT_ENGINE // Interal Flag used by MSS to output text rather than Audio internal bool _outputAsText; #endif #endregion //******************************************************************* // // Private Members // //******************************************************************* #region Private Members // //=== ISpThreadTask =============================================================== // // These methods implement the ISpThreadTask interface. They will all be called on // a worker thread. /// /// This method is the task proc used for text rendering and for event /// forwarding. It may be called on a worker thread for asynchronous speaking, or /// it may be called on the client thread for synchronous speaking. If it is /// called on the client thread, the hExitThreadEvent handle will be null. /// void ThreadProc () { while (true) { Parameters parameters; _evtPendingSpeak.WaitOne (); //--- Get the next speak item lock (_pendingSpeakQueue) { if (_pendingSpeakQueue.Count > 0) { parameters = _pendingSpeakQueue.Dequeue (); ParametersSpeak paramSpeak = parameters._parameter as ParametersSpeak; if (paramSpeak != null) { lock (_site) { if (_currentPrompt == null && State != SynthesizerState.Paused) { OnStateChanged (SynthesizerState.Speaking); } _currentPrompt = paramSpeak._prompt; _waveOut.IsAborted = false; } } else { _currentPrompt = null; } } else { parameters = null; } } // The client thread may have cleared the list to abort the audio if (parameters != null) { switch (parameters._action) { case Action.GetVoice: { try { _pendingVoice = null; _pendingException = null; _pendingVoice = GetProxyEngine ((VoiceInfo) parameters._parameter); } #pragma warning disable 6500 catch (Exception e) { // this thread cannot be terminated. _pendingException = e; } #pragma warning restore 6500 finally { // unlock the client _evtPendingGetProxy.Set (); } } break; case Action.SpeakText: { ParametersSpeak paramSpeak = (ParametersSpeak) parameters._parameter; try { InjectEvent (TtsEventId.StartInputStream, paramSpeak._prompt, paramSpeak._prompt._exception, null); if (paramSpeak._prompt._exception == null) { // No lexicon yet Listlexicons = new List (); ; //--- Create a single speak info structure for all the text TTSVoice voice = _currentVoice != null ? _currentVoice : GetVoice (false); //--- Create the speak info SpeakInfo speakInfo = new SpeakInfo (this, voice); if (paramSpeak._textToSpeak != null) { //--- Make sure we have a voice defined by now if (!paramSpeak._isXml) { FragmentState fragmentState = new FragmentState (); fragmentState.Action = TtsEngineAction.Speak; fragmentState.Prosody = new Prosody (); TextFragment textFragment = new TextFragment (fragmentState, string.Copy (paramSpeak._textToSpeak)); speakInfo.AddText (voice, textFragment); } else { TextFragmentEngine engine = new TextFragmentEngine (speakInfo, paramSpeak._textToSpeak, _pexml, _resourceLoader, lexicons); SsmlParser.Parse (paramSpeak._textToSpeak, engine, speakInfo.Voice); } } else { speakInfo.AddAudio (new AudioData (paramSpeak._audioFile, _resourceLoader)); } // Add the global synthesizer lexicon lexicons.AddRange (_lexicons); System.Diagnostics.Debug.Assert (speakInfo != null); SpeakText (speakInfo, paramSpeak._prompt, lexicons); } ChangeStateToReady (paramSpeak._prompt, paramSpeak._prompt._exception); } #pragma warning disable 6500 catch (Exception e) { //--- Always inject the end of stream and complete even on failure // Note: we're not getting the return codes from these so we // don't overwrite a possible error from above. Also we // really don't care about these errors. ChangeStateToReady (paramSpeak._prompt, e); } } break; #pragma warning restore 6500 #if SPEECHSERVER || PROMPT_ENGINE case Action.LoadDatabase: try { _pendingException = null; ParametersDatabase paramLoad = (ParametersDatabase) parameters._parameter; PromptEngine.LoadDatabase (paramLoad._localName, paramLoad._alias); } #pragma warning disable 6500 catch (Exception e) { // this thread cannot be terminated. _pendingException = e; } #pragma warning restore 6500 finally { // unlock the client _evtPendingGetProxy.Set (); } break; case Action.UnloadDatabase: try { _pendingException = null; PromptEngine.UnloadDatabase ((string) parameters._parameter); } #pragma warning disable 6500 catch (Exception e) { // this thread cannot be terminated. _pendingException = e; } #pragma warning restore 6500 finally { // unlock the client _evtPendingGetProxy.Set (); } break; #endif default: System.Diagnostics.Debug.Assert (false, "Unknown Action!"); break; } } //--- Get the next speak item lock (_pendingSpeakQueue) { // if nothing left then reset the wait handle. if (_pendingSpeakQueue.Count == 0) { _evtPendingSpeak.Reset (); } } // check if we need to terminate this thread if (_fExitWorkerThread) { _synthesizerState = SynthesizerState.Ready; break; } } } private void AddSpeakParameters (Parameters param) { lock (_pendingSpeakQueue) { _pendingSpeakQueue.Enqueue (param); // Start the worker thread if the list was empty if (_pendingSpeakQueue.Count == 1) { _evtPendingSpeak.Set (); } } } /// /// This method renders the current speak info structure. It may be /// made up of one or more speech segements, each intended for a different /// voice/engine. /// private void SpeakText (SpeakInfo speakInfo, Prompt prompt, Listlexicons) { VoiceInfo currrentVoiceId = null; //=== Main processing loop =========================================== for (SpeechSeg speechSeg; (speechSeg = speakInfo.RemoveFirst ()) != null; ) { TTSVoice voice; //--- Update the current rendering engine voice = speechSeg.Voice; // Fire the voice change object token if necessary if (voice != null && (currrentVoiceId == null || !currrentVoiceId.Equals (voice.VoiceInfo))) { currrentVoiceId = voice.VoiceInfo; InjectEvent (TtsEventId.VoiceChange, prompt, null, currrentVoiceId); } lock (_processingSpeakLock) { if (speechSeg.IsText) { //--- Speak the segment lock (_site) { if (_waveOut.IsAborted) { _waveOut.IsAborted = false; //--- Always inject the end of stream and complete event on failure throw new OperationCanceledException (SR.Get (SRID.PromptAsyncOperationCancelled)); } _site.InitRun (_waveOut, _defaultRate, prompt); _waveOut.Begin (voice.WaveFormat (_waveOut.WaveFormat)); } // Set the Lexicons if any try { // Update the lexicon and set the default events to trap voice.UpdateLexicons (lexicons); _site.SetEventsInterest (_ttsInterest); // Calls GetOutputFormat if needed on the TTS engine byte [] outputWaveFormat = voice.WaveFormat (_waveOut.WaveFormat); // Get the TTS engine or a backup voice ITtsEngineProxy engineProxy = voice.TtsEngine; #if !SPEECHSERVER // Set the events specific to the desktop if ((_ttsInterest & (1 << (int) TtsEventId.Phoneme)) != 0 && engineProxy.EngineAlphabet != AlphabetType.Ipa) { _site.EventMapper = new PhonemeEventMapper (_site, PhonemeEventMapper.PhonemeConversion.SapiToIpa, engineProxy.AlphabetConverter); } else { _site.EventMapper = null; } #else if (_outputAsText) { engineProxy = new TextEngine (_site, 0); } if (speechSeg.ContainsPrompEngineFragment) { // Set the backup voice if the text to speak contains some prompt elements ITtsEngineProxy voiceEngine = engineProxy; engineProxy = PromptEngineProxy; voiceEngine.BackupVoice (_promptEngine); if (_outputAsText) { // If text is requested, set the default TTS engine as outputing raw text. PromptEngineProxy.GetOutputFormat (IntPtr.Zero); } else { // Set the desired audio format SetPromptEngineOutputFormat (); } } #endif // Call the TTS engine to perform the speak through the proxy layer that // converts SSML fragments to whatever the TTS engine supports _site.LastException = null; engineProxy.Speak (speechSeg.FragmentList, outputWaveFormat); } finally { _waveOut.WaitUntilDone (); _waveOut.End (); } } else { System.Diagnostics.Debug.Assert (speechSeg.Audio != null); _waveOut.PlayWaveFile (speechSeg.Audio); // Done with the audio, release the underlying stream speechSeg.Audio.Dispose (); } lock (_site) { // The current prompt has now been played _currentPrompt = null; // Check for abort or errors during the play if (_waveOut.IsAborted || _site.LastException != null) { _waveOut.IsAborted = false; if (_site.LastException != null) { Exception lastException = _site.LastException; _site.LastException = null; throw lastException; } //--- Always inject the end of stream and complete event on failure throw new OperationCanceledException (SR.Get (SRID.PromptAsyncOperationCancelled)); } } } } } /* SpeakText */ /// /// Get the user's default rate from the registry /// private static UInt32 GetDefaultRate () { //--- Read the current user's default rate UInt32 lCurrRateAd = 0; using (ObjectTokenCategory category = ObjectTokenCategory.Create (SAPICategories.CurrentUserVoices)) { if (category != null) { category.TryGetDWORD (defaultVoiceRate, ref lCurrRateAd); } } return lCurrRateAd; } // private void InjectEvent (TtsEventId evtId, Prompt prompt, Exception exception, VoiceInfo voiceInfo) { // If the prompt is terminated, release it ASAP if (evtId == TtsEventId.EndInputStream) { if (_site.EventMapper != null) { _site.EventMapper.FlushEvent (); } prompt._exception = exception; } int evtMask = 1 << (int) evtId; if ((evtMask & _ttsInterest) != 0) { TTSEvent ttsEvent = new TTSEvent (evtId, prompt, exception, voiceInfo); _asyncWorker.Post (ttsEvent); } } ////// Calls the client notification delegate. /// /// private void OnStateChanged (SynthesizerState state) { if (_synthesizerState != state) { // Keep the last state SynthesizerState previousState = _synthesizerState; _synthesizerState = state; // Fire the events if (_eventStateChanged != null) { _asyncWorker.PostOperation (_eventStateChanged, new StateChangedEventArgs (state, previousState)); } } } ////// Set the state to ready if nothing anymore needs to be spoken. /// private void ChangeStateToReady (Prompt prompt, Exception exception) { lock (_waveOut) { //--- Get the next speak item lock (_pendingSpeakQueue) { // if nothing left then reset the wait handle. if (_pendingSpeakQueue.Count == 0) { _currentPrompt = null; System.Diagnostics.Debug.Assert (State == SynthesizerState.Speaking || State == SynthesizerState.Paused); if (State != SynthesizerState.Paused) { // Keep the last state SynthesizerState previousState = _synthesizerState; _synthesizerState = SynthesizerState.Ready; // Fire the notification for end of prompt InjectEvent (TtsEventId.EndInputStream, prompt, exception, null); if (_eventStateChanged != null) { _asyncWorker.PostOperation (_eventStateChanged, new StateChangedEventArgs (_synthesizerState, previousState)); } } else { // Pause mode. Send a single notification for end of prompt InjectEvent (TtsEventId.EndInputStream, prompt, exception, null); } } else { // More prompts to play. // Send a single notification that this one is over. InjectEvent (TtsEventId.EndInputStream, prompt, exception, null); } } } } ////// This method is used to create the Engine voice and initialize /// ///private TTSVoice GetVoice (VoiceInfo voiceInfo, bool switchContext) { TTSVoice voice = null; lock (_voiceDictionary) { if (!_voiceDictionary.TryGetValue (voiceInfo, out voice)) { if (switchContext) { ExecuteOnBackgroundThread (Action.GetVoice, voiceInfo); // Voice is null if exception occured voice = _pendingException == null ? _pendingVoice : null; } else { // Get the voice voice = GetProxyEngine (voiceInfo); } } } return voice; } private void ExecuteOnBackgroundThread (Action action, object parameter) { //--- Get the voice on the worker thread lock (_pendingSpeakQueue) { _evtPendingGetProxy.Reset (); _pendingSpeakQueue.Enqueue (new Parameters (action, parameter)); // Start the worker thread if the list was empty if (_pendingSpeakQueue.Count == 1) { _evtPendingSpeak.Set (); } } _evtPendingGetProxy.WaitOne (); } private TTSVoice GetEngineWithVoice (TTSVoice defaultVoice, VoiceInfo defaultVoiceId, string name, CultureInfo culture, VoiceGender gender, VoiceAge age, int variant, bool switchContext) { TTSVoice voice = null; // The list of enabled voices can be changed by a speech application lock (_enabledVoicesLock) { // Do we have a name? if (!string.IsNullOrEmpty (name)) { // try to find a voice for a given name voice = MatchVoice (name, variant, switchContext); } // Still no voice loop to find a matching one. if (voice == null) { InstalledVoice viDefault = null; // Easy out if the voice is the default voice if (defaultVoice != null || defaultVoiceId != null) { // try to select the default voice viDefault = InstalledVoice.Find (_installedVoices, defaultVoice != null ? defaultVoice.VoiceInfo : defaultVoiceId); if (viDefault != null && viDefault.Enabled && variant == 1) { VoiceInfo vi = viDefault.VoiceInfo; if (viDefault.Enabled && vi.Culture.Equals (culture) && (gender == VoiceGender.NotSet || gender == VoiceGender.Neutral || gender == vi.Gender) && (age == VoiceAge.NotSet || age == vi.Age)) { voice = defaultVoice; } } } // Pick the first one in the list as the backup default while (voice == null && _installedVoices.Count > 0) { if (viDefault == null) { viDefault = InstalledVoice.FirstEnabled (_installedVoices, CultureInfo.CurrentUICulture); } if (viDefault != null) { voice = MatchVoice (culture, gender, age, variant, switchContext, ref viDefault); } else { break; } } } //--- Create the default voice if (voice == null) { if (defaultVoice == null) { throw new InvalidOperationException (SR.Get (SRID.SynthesizerVoiceFailed)); } else { voice = defaultVoice; } } } return voice; } /// /// Try to find a voice for a given name /// /// /// /// ///private TTSVoice MatchVoice (string name, int variant, bool switchContext) { TTSVoice voice = null; // Look for it in the object tokens VoiceInfo voiceInfo = null; int cVariant = variant; foreach (InstalledVoice sysVoice in _installedVoices) { int firstCharacter; if (sysVoice.Enabled && (firstCharacter = name.IndexOf (sysVoice.VoiceInfo.Name, StringComparison.Ordinal)) >= 0) { int lastCharacter = firstCharacter + sysVoice.VoiceInfo.Name.Length; if ((firstCharacter == 0 || name [firstCharacter - 1] == ' ') && (lastCharacter == name.Length || name [lastCharacter] == ' ')) { voiceInfo = sysVoice.VoiceInfo; if (cVariant-- == 1) { break; } } } } // If we had a name, try to get engine from it if (voiceInfo != null) { // Do we already have an voice for this voiceInfo? voice = GetVoice (voiceInfo, switchContext); } return voice; } private TTSVoice MatchVoice (CultureInfo culture, VoiceGender gender, VoiceAge age, int variant, bool switchContext, ref InstalledVoice viDefault) { TTSVoice voice = null; // Build a list with all the tokens List tokens = new List (_installedVoices); // Remove all the voices that are disabled for (int i = tokens.Count - 1; i >= 0; i--) { if (!tokens [i].Enabled) { tokens.RemoveAt (i); } } // Try to select the best available voice for (; voice == null && tokens.Count > 0; ) { InstalledVoice sysVoice = MatchVoice (viDefault, culture, gender, age, variant, tokens); if (sysVoice != null) { // Find a voice and a match engine! voice = GetVoice (sysVoice.VoiceInfo, switchContext); if (voice == null) { // The voice associated with this token cannot be instanciated. // Remove it from the list of posssible voices tokens.Remove (sysVoice); sysVoice.SetEnabledFlag (false, switchContext); if (sysVoice == viDefault) { viDefault = null; } } break; } } return voice; } private static InstalledVoice MatchVoice (InstalledVoice defaultTokenInfo, CultureInfo culture, VoiceGender gender, VoiceAge age, int variant, List tokensInfo) { // Set the default return value InstalledVoice sysVoice = defaultTokenInfo; int bestMatch = CalcMatchValue (culture, gender, age, sysVoice.VoiceInfo); int iPosDefault = -1; // calc the best possible match for (int iToken = 0; iToken < tokensInfo.Count; iToken++) { InstalledVoice ti = tokensInfo [iToken]; if (ti.Enabled) { int matchValue = CalcMatchValue (culture, gender, age, ti.VoiceInfo); if (ti.Equals (defaultTokenInfo)) { iPosDefault = iToken; } // Is this a better match? if (matchValue > bestMatch) { sysVoice = ti; bestMatch = matchValue; } // If we cannot get a better voice, exit if (matchValue == 0x7 && (variant == 1 || iPosDefault >= 0)) { break; } } } if (variant > 1) { // Set the default voice as the first entry tokensInfo [iPosDefault] = tokensInfo [0]; tokensInfo [0] = defaultTokenInfo; int requestedVariant = variant; do { foreach (InstalledVoice ti in tokensInfo) { if (ti.Enabled && CalcMatchValue (culture, gender, age, ti.VoiceInfo) == bestMatch) { // If we are looking for a variant and are matching the best match, switch voice --variant; sysVoice = ti; } if (variant == 0) { break; } } // if the variant number is large, calc the modulo and restart from there if (variant > 0) { variant = requestedVariant % (requestedVariant - variant); } } while (variant > 0); } return sysVoice; } private static int CalcMatchValue (CultureInfo culture, VoiceGender gender, VoiceAge age, VoiceInfo voiceInfo) { int matchValue; if (voiceInfo != null) { matchValue = 0; CultureInfo tokCulture = voiceInfo.Culture; if (culture != null && Helpers.CompareInvariantCulture (tokCulture, culture)) { // Exact Culture match has priority over gender and age. if (culture.Equals (tokCulture)) { matchValue |= 0x4; } // Male / Female has priority ove age if (gender == VoiceGender.NotSet || voiceInfo.Gender == gender) { matchValue |= 0x2; } // Age check if (age == VoiceAge.NotSet || voiceInfo.Age == age) { matchValue |= 0x1; } } } else { matchValue = -1; } return matchValue; } private TTSVoice GetProxyEngine (VoiceInfo voiceInfo) { // Create the TTS voice // Try to get a managed SSML engine ITtsEngineProxy engineProxy = GetSsmlEngine (voiceInfo); // Try to get a COM engine if (engineProxy == null) { engineProxy = GetComEngine (voiceInfo); } // store the proxy object TTSVoice voice = null; if (engineProxy != null) { voice = new TTSVoice (engineProxy, voiceInfo); _voiceDictionary.Add (voiceInfo, voice); } #if SPEECHSERVER && !SERVERTESTDLL if (voiceInfo.VoiceCategory == VoiceCategory.ScanSoft) { if (!TryScanSoftHandshake (voice)) { voice = null; } } #endif return voice; } private ITtsEngineProxy GetSsmlEngine (VoiceInfo voiceInfo) { // Try first to get a TtsEngineSsml for it ITtsEngineProxy engineProxy = null; try { Assembly assembly; if (!string.IsNullOrEmpty (voiceInfo.AssemblyName) && (assembly = Assembly.Load (voiceInfo.AssemblyName)) != null) { // Type [] types = assembly.GetTypes (); TtsEngineSsml ssmlEngine = null; foreach (Type type in types) { if (type.IsSubclassOf (typeof (TtsEngineSsml))) { string [] args = new string [] { voiceInfo.Clsid }; ssmlEngine = assembly.CreateInstance (type.ToString (), false, BindingFlags.Default, null, args, CultureInfo.CurrentUICulture, null) as TtsEngineSsml; break; } } if (ssmlEngine != null) { // Create the engine site if not yet available engineProxy = new TtsProxySsml (ssmlEngine, _site, voiceInfo.Culture.LCID); } } } catch (ArgumentException) { } catch (IOException) { } catch (BadImageFormatException) { } return engineProxy; } private ITtsEngineProxy GetComEngine (VoiceInfo voiceInfo) { ITtsEngineProxy engineProxy = null; try { ObjectToken token = ObjectToken.Create (null, voiceInfo.RegistryKeyPath, false); if (token != null) { object engine = token.CreateObjectFromToken
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- SqlAliasesReferenced.cs
- SelectionHighlightInfo.cs
- WebPartDisplayModeCancelEventArgs.cs
- DrawingContextDrawingContextWalker.cs
- TypedTableBase.cs
- CallInfo.cs
- ContainerAction.cs
- DefaultHttpHandler.cs
- Stroke.cs
- CustomErrorCollection.cs
- SettingsProperty.cs
- Int16KeyFrameCollection.cs
- MainMenu.cs
- EntitySqlException.cs
- LocalBuilder.cs
- CheckBoxRenderer.cs
- SqlExpander.cs
- WebRequestModuleElementCollection.cs
- FlatButtonAppearance.cs
- ScrollBar.cs
- FlagsAttribute.cs
- ExpandSegmentCollection.cs
- ResXDataNode.cs
- DataGridCaption.cs
- WindowsTitleBar.cs
- SslStream.cs
- WebServiceTypeData.cs
- WebBaseEventKeyComparer.cs
- TemplateField.cs
- XmlUTF8TextWriter.cs
- WebRequestModuleElementCollection.cs
- WebPartCatalogAddVerb.cs
- SiteOfOriginContainer.cs
- ThreadAttributes.cs
- DataAdapter.cs
- StopRoutingHandler.cs
- PersonalizationProvider.cs
- ReadWriteSpinLock.cs
- LineBreak.cs
- DrawingContextDrawingContextWalker.cs
- SmtpFailedRecipientException.cs
- CodeIterationStatement.cs
- AdditionalEntityFunctions.cs
- Mappings.cs
- PageThemeBuildProvider.cs
- PartialToken.cs
- WaveHeader.cs
- WsdlInspector.cs
- X509ChainPolicy.cs
- ThreadLocal.cs
- Message.cs
- ComplexTypeEmitter.cs
- Error.cs
- CallbackValidator.cs
- HttpChannelFactory.cs
- HyperLinkField.cs
- Grid.cs
- ToolboxItemWrapper.cs
- SafeNativeMethods.cs
- BindingExpression.cs
- IIS7WorkerRequest.cs
- _LocalDataStoreMgr.cs
- WorkflowInstance.cs
- XmlValueConverter.cs
- XmlImplementation.cs
- KeyMatchBuilder.cs
- ValidationEventArgs.cs
- HandleCollector.cs
- EntityDataSourceStatementEditor.cs
- XmlChoiceIdentifierAttribute.cs
- TemplateXamlTreeBuilder.cs
- SqlMethods.cs
- SplitContainer.cs
- SingleConverter.cs
- DbMetaDataFactory.cs
- BaseResourcesBuildProvider.cs
- InfoCardClaim.cs
- InputLanguageSource.cs
- WebPartZoneCollection.cs
- TextContainer.cs
- PackageProperties.cs
- CommandExpr.cs
- EmptyCollection.cs
- GridLengthConverter.cs
- CopyNamespacesAction.cs
- StreamAsIStream.cs
- TouchDevice.cs
- OleDbStruct.cs
- SelectionPattern.cs
- CRYPTPROTECT_PROMPTSTRUCT.cs
- Dynamic.cs
- AuthenticationModuleElementCollection.cs
- RefreshResponseInfo.cs
- IncomingWebResponseContext.cs
- WebPermission.cs
- HandleRef.cs
- ImpersonationContext.cs
- ToolStripItemBehavior.cs
- EncoderFallback.cs
- SelfIssuedAuthAsymmetricKey.cs