예제 #1
0
        //---------------------------------------------------------------------
        // Static members

        /// <summary>
        /// Reads the text-to-speech engine from the application's configuration
        /// using the specified key prefix.
        /// </summary>
        /// <param name="keyPrefix">The application configuration key prefix.</param>
        /// <returns>The settings.</returns>
        /// <remarks>
        /// <para>
        /// The text-to-speech engine settings are loaded from the application
        /// configuration, using the specified key prefix.  The following
        /// settings are recognized by the class:
        /// </para>
        /// <div class="tablediv">
        /// <table class="dtTABLE" cellspacing="0" ID="Table1">
        /// <tr valign="top">
        /// <th width="1">Setting</th>
        /// <th width="1">Default</th>
        /// <th width="90%">Description</th>
        /// </tr>
        /// <tr valign="top">
        ///     <td>PhraseCacheFolder</td>
        ///     <td><b>$(ProgramDataPath)\LillTek\NeonSwitch\PhraseCache</b></td>
        ///     <td>
        ///     The fully qualified path to the file system folder where the cache will be located.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>OneTimePhraseFolder</td>
        ///     <td><b>$(ProgramDataPath)\LillTek\NeonSwitch\PhraseCache\OneTime</b></td>
        ///     <td>
        ///     The fully qualified path to the file system folder where one-time
        ///     phrases will be located while they are played by NeonSwitch.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>PhraseFolderFanout</td>
        ///     <td><b>100</b></td>
        ///     <td>
        ///     Specifies the number of subfolders to create in the cache.  Cached audio
        ///     files will be distributed randomly across these folders to avoid having a huge
        ///     number of files in any one folder.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>PhrasePurgeInterval</td>
        ///     <td><b>1m</b></td>
        ///     <td>
        ///     The time interval at which temporary phrases as well as phrases that
        ///     have not been referenced for some period of time will be purged.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>MaxPhraseTTL</td>
        ///     <td><b>1d</b></td>
        ///     <td>
        ///     The maximum time a phrase will be cached if it is not used.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>MaxOneTimePhraseTTL</td>
        ///     <td><b>5m</b></td>
        ///     <td>
        ///     The maximum time a one-time phrase audio file will be retained.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>DefaultVoice</td>
        ///     <td><b>auto</b></td>
        ///     <td>
        ///     <para>
        ///     The default speech synthesis voice.  This defaults to <b>auto</b>
        ///     which has the switch choose the voice depending on which voices are
        ///     currently installed.
        ///     </para>
        ///     <para>
        ///     NeonSwitch will favor Cepstral 8KHz voices if present, especially
        ///     <b>Cepstral Allison-8kHz</b> but fall back to <b>Microsoft Anna</b>
        ///     which should be present on all Windows machines.
        ///     </para>
        ///     </td>
        /// </tr>
        /// </table>
        /// </div>
        /// </remarks>
        public static SpeechEngineSettings LoadConfig(string keyPrefix)
        {
            var config   = new Config(keyPrefix);
            var settings = new SpeechEngineSettings();

            settings.PhraseCacheFolder   = config.Get("PhraseCacheFolder", settings.PhraseCacheFolder);
            settings.OneTimePhraseFolder = config.Get("OneTimePhraseFolder", settings.OneTimePhraseFolder);
            settings.PhraseFolderFanout  = config.Get("PhraseFolderFanout", settings.PhraseFolderFanout);
            settings.PhrasePurgeInterval = config.Get("PhrasePurgeInterval", settings.PhrasePurgeInterval);
            settings.MaxPhraseTTL        = config.Get("MaxPhraseTTL", settings.MaxPhraseTTL);
            settings.MaxOneTimePhraseTTL = config.Get("MaxOneTimePhraseTTL", settings.MaxOneTimePhraseTTL);
            settings.DefaultVoice        = config.Get("DefaultVoice", settings.DefaultVoice);

            return(settings);
        }
예제 #2
0
        /// <summary>
        /// Constructs and initializes the phrase cache using TTS engine
        /// settings passed.
        /// </summary>
        /// <param name="settings">The engine settings.</param>
        public PhraseCache(SpeechEngineSettings settings)
        {
            this.settings          = settings;
            this.isRunning         = true;
            this.index             = new Dictionary <string, Phrase>();
            this.purgeTimer        = new PolledTimer(settings.PhrasePurgeInterval, true);
            this.oneTimePurgeTimer = new PolledTimer(Helper.Divide(settings.MaxOneTimePhraseTTL, 2));

            // Make sure the cache folders exist.

            Helper.CreateFolderTree(settings.PhraseCacheFolder);
            Helper.CreateFolderTree(settings.OneTimePhraseFolder);

            for (int i = 0; i < settings.PhraseFolderFanout; i++)
            {
                Helper.CreateFolderTree(Path.Combine(settings.PhraseCacheFolder, GetSubfolderName(i)));
            }

            // Initialize the cache.

            PurgeOneTime();
            LoadIndex();
        }
예제 #3
0
        private static Dictionary <string, string> voiceNameMap;        // Maps friendly voice names to the internal name

        /// <summary>
        /// Starts the engine if it is not already running.
        /// </summary>
        /// <param name="settings">The engine settings.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="settings"/> is <c>null</c>.</exception>
        public static void Start(SpeechEngineSettings settings)
        {
            if (settings == null)
            {
                throw new ArgumentNullException("settings");
            }

            lock (syncLock)
            {
                if (SpeechEngine.isRunning)
                {
                    return;
                }

                SpeechEngine.settings  = settings;
                SpeechEngine.cache     = new PhraseCache(settings);
                SpeechEngine.isRunning = true;

                // Create the default audio formats.

                format_8000KHz  = new SpeechAudioFormatInfo(8000, AudioBitsPerSample.Eight, AudioChannel.Mono);
                format_11025KHz = new SpeechAudioFormatInfo(11025, AudioBitsPerSample.Eight, AudioChannel.Mono);
                format_16000KHz = new SpeechAudioFormatInfo(16000, AudioBitsPerSample.Eight, AudioChannel.Mono);

                // Get the fully qualified paths to the error files.

                noVoicesPath   = Path.Combine(CoreApp.InstallPath, "Audio", "NoVoicesError.wav");
                synthErrorPath = Path.Combine(CoreApp.InstallPath, "Audio", "SpeechSynthError.wav");

                // Enumerate the installed voices and select the default voice.
                //
                // Note: The Microsoft Speech Platform voices have really clunky names like:
                //
                //    "Microsoft Server Speech Text to Speech Voice (en-AU, Hayley)"
                //
                // I'm going to simplify these to be just "Microsoft <name>" and maintain
                // a table that maps back to the original name.

                voiceNameMap = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);

                using (var synth = new SpeechSynthesizer())
                {
                    var voices = new Dictionary <string, VoiceInfo>(StringComparer.OrdinalIgnoreCase);

                    foreach (var voice in synth.GetInstalledVoices())
                    {
                        var voiceName = voice.VoiceInfo.Name;

                        if (!voice.Enabled)
                        {
                            continue;
                        }

                        // $hack(jeff.lill):
                        //
                        // Make sure that the voice can actually be used.  I've run into
                        // situations where [Microsoft Anna] was present and enabled but
                        // could not be selected.  I believe this may be a 64-bit issue
                        // or perhaps installing Cepstral voices messes with Anna.

                        try
                        {
                            synth.SelectVoice(voice.VoiceInfo.Name);
                        }
                        catch
                        {
                            continue;
                        }

                        if (voiceName.StartsWith("Microsoft Server Speech Text to Speech Voice ("))
                        {
                            int p = voiceName.IndexOf(',');

                            if (p != -1)
                            {
                                voiceName = voiceName.Substring(p + 1);
                                voiceName = "Microsoft " + voiceName.Replace(")", string.Empty).Trim();

                                voiceNameMap[voiceName] = voice.VoiceInfo.Name;
                            }
                        }

                        voices.Add(voiceName, voice.VoiceInfo);
                    }

                    SpeechEngine.InstalledVoices  = voices.ToReadOnly();
                    SpeechEngine.DefaultVoice     = null;
                    SpeechEngine.DefaultVoiceInfo = null;

                    // First see if the desired default voice exists.

                    if (!string.IsNullOrWhiteSpace(settings.DefaultVoice) && String.Compare(settings.DefaultVoice, "auto") != 0)
                    {
                        VoiceInfo voiceInfo;

                        if (voices.TryGetValue(settings.DefaultVoice, out voiceInfo))
                        {
                            SpeechEngine.DefaultVoice = voiceInfo.Name;
                        }
                        else
                        {
                            SysLog.LogWarning("[SpeechEngine] was not able to locate the requested default voice [{0}].  Another voice will be selected automatically.", settings.DefaultVoice);
                        }
                    }

                    // If not look for an alternative

                    if (SpeechEngine.DefaultVoice == null)
                    {
                        if (voices.ContainsKey("Microsoft Helen"))
                        {
                            SpeechEngine.DefaultVoice = "Microsoft Helen";
                        }
                        else if (voices.ContainsKey("Microsoft Anna"))
                        {
                            SpeechEngine.DefaultVoice = "Microsoft Anna";
                        }
                        else
                        {
                            SysLog.LogWarning("[SpeechEngine] was not able to locate the [Microsoft Anna] voice.");

                            var v = voices.Keys.FirstOrDefault();

                            if (v == null)
                            {
                                SpeechEngine.DefaultVoice = null;
                                SysLog.LogError("[SpeechEngine] was not able to locate any speech synthesis voices.  Speech synthesis will be disabled.");
                            }
                            else
                            {
                                SpeechEngine.DefaultVoice = v;
                            }
                        }
                    }

                    if (SpeechEngine.DefaultVoice != null)
                    {
                        SpeechEngine.DefaultVoiceInfo = SpeechEngine.InstalledVoices[GetVoice(SpeechEngine.DefaultVoice)];
                    }
                }
            }
        }
예제 #4
0
        /// <summary>
        /// Starts the service, associating it with an <see cref="IServiceHost" /> instance.
        /// </summary>
        /// <param name="serviceHost">The service user interface.</param>
        /// <param name="args">Command line arguments.</param>
        public void Start(IServiceHost serviceHost, string[] args)
        {
            lock (syncLock)
            {
                if (router != null)
                {
                    return;     // Already started
                }
                // Global initialization

                startTime = DateTime.UtcNow;

                NetTrace.Start();
                CoreApp.InstallPerfCounters();

                // Service initialization

                this.serviceHost = serviceHost;

                try
                {
                    SysLog.LogInformation("NeonSwitch v{0} Start", Helper.GetVersionString());

                    router = new LeafRouter();
                    router.Start();

                    state = ServiceState.Running;

                    bkTimer = new GatedTimer(OnBkTimer, null, CoreApp.BkTaskInterval);
                    SpeechEngine.Start(SpeechEngineSettings.LoadConfig("NeonSwitch.Speech"));
#if DEBUG
                    // $todo(jeff.lill): Delete this.

                    SwitchTest.Test();
#endif
                    // Indicate that the switch core service is open for business.  NeonSwitch
                    // application instance loaders will spin, waiting for this to be set before
                    // calling the application's main function.

                    Switch.SetGlobal(SwitchGlobal.NeonSwitchReady, "true");
                }
                catch (Exception e)
                {
                    SpeechEngine.Stop();

                    if (bkTimer != null)
                    {
                        bkTimer.Dispose();
                        bkTimer = null;
                    }

                    if (router != null)
                    {
                        router.Stop();
                        router = null;
                    }

                    SysLog.LogException(e);
                    throw;
                }
            }
        }