Exemplo n.º 1
0
        public void Speak(string speech, string voice, int echoDelay, int distortionLevel, int chorusLevel, int reverbLevel, int compressLevel, bool wait = true, int priority = 3)
        {
            if (speech == null)
            {
                return;
            }

            Thread speechThread = new Thread(() =>
            {
                try
                {
                    using (SpeechSynthesizer synth = new SpeechSynthesizer())
                        using (MemoryStream stream = new MemoryStream())
                        {
                            if (string.IsNullOrWhiteSpace(voice))
                            {
                                voice = configuration.StandardVoice;
                            }
                            if (voice != null)
                            {
                                try
                                {
                                    synth.SelectVoice(voice);
                                }
                                catch { }
                            }

                            synth.Rate   = configuration.Rate;
                            synth.Volume = configuration.Volume;

                            synth.StateChanged += new EventHandler <StateChangedEventArgs>(synth_StateChanged);
                            synth.SetOutputToWaveStream(stream);
                            if (speech.Contains("<phoneme") || speech.Contains("<break"))
                            {
                                string finalSpeech = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xml:lang=\"" + bestGuessCulture(synth) + "\"><s>" + speech + "</s></speak>";
                                Logging.Debug("SSML speech: " + finalSpeech);
                                try
                                {
                                    synth.SpeakSsml(finalSpeech);
                                }
                                catch
                                {
                                    Logging.Error("Best guess culture of " + bestGuessCulture(synth) + " for voice " + synth.Voice.Name + " was incorrect");
                                    synth.SpeakSsml(finalSpeech);
                                }
                            }
                            else
                            {
                                Logging.Debug("Speech: " + speech);
                                synth.Speak(speech);
                            }
                            stream.Seek(0, SeekOrigin.Begin);

                            IWaveSource source = new WaveFileReader(stream);

                            // We need to extend the duration of the wave source if we have any effects going on
                            if (chorusLevel != 0 || reverbLevel != 0 || echoDelay != 0)
                            {
                                // Add a base of 500ms plus 10ms per effect level over 50
                                source = source.AppendSource(x => new ExtendedDurationWaveSource(x, 500 + Math.Max(0, (configuration.EffectsLevel - 50) * 10)));
                            }

                            // Add various effects...

                            // We always have chorus
                            if (chorusLevel != 0)
                            {
                                source = source.AppendSource(x => new DmoChorusEffect(x)
                                {
                                    Depth = chorusLevel, WetDryMix = Math.Min(100, (int)(180 * ((decimal)configuration.EffectsLevel) / ((decimal)100))), Delay = 16, Frequency = (configuration.EffectsLevel / 10), Feedback = 25
                                });
                            }

                            // We only have reverb and echo if we're not transmitting or receiving
                            //if (!radio)
                            //{
                            if (reverbLevel != 0)
                            {
                                // We tone down the reverb level with the distortion level, as the combination is nasty
                                source = source.AppendSource(x => new DmoWavesReverbEffect(x)
                                {
                                    ReverbTime = (int)(1 + 999 * ((decimal)configuration.EffectsLevel) / ((decimal)100)), ReverbMix = Math.Max(-96, -96 + (96 * reverbLevel / 100) - distortionLevel)
                                });
                            }

                            if (echoDelay != 0)
                            {
                                // We tone down the echo level with the distortion level, as the combination is nasty
                                source = source.AppendSource(x => new DmoEchoEffect(x)
                                {
                                    LeftDelay = echoDelay, RightDelay = echoDelay, WetDryMix = Math.Max(5, (int)(10 * ((decimal)configuration.EffectsLevel) / ((decimal)100)) - distortionLevel), Feedback = Math.Max(0, 10 - distortionLevel / 2)
                                });
                            }
                            //}

                            if (configuration.EffectsLevel > 0 && distortionLevel > 0)
                            {
                                source = source.AppendSource(x => new DmoDistortionEffect(x)
                                {
                                    Edge = distortionLevel, Gain = -distortionLevel / 2, PostEQBandwidth = 4000, PostEQCenterFrequency = 4000
                                });
                            }

                            //if (radio)
                            //{
                            //    source = source.AppendSource(x => new DmoDistortionEffect(x) { Edge = 7, Gain = -distortionLevel / 2, PostEQBandwidth = 2000, PostEQCenterFrequency = 6000 });
                            //    source = source.AppendSource(x => new DmoCompressorEffect(x) { Attack = 1, Ratio = 3, Threshold = -10 });
                            //}

                            if (priority < activeSpeechPriority)
                            {
                                StopCurrentSpeech();
                            }

                            EventWaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
                            var soundOut = new WasapiOut();
                            soundOut.Initialize(source);
                            soundOut.Stopped += (s, e) => waitHandle.Set();

                            StartSpeech(soundOut, priority);

                            Logging.Debug("Starting speech");
                            soundOut.Play();

                            // Add a timeout, in case it doesn't come back with the signal
                            waitHandle.WaitOne(source.GetTime(source.Length));

                            StopCurrentSpeech();
                            source.Dispose();
                        }
                }
                catch (Exception ex)
                {
                    Logging.Error("Failed to speak: " + ex);
                }
            });

            speechThread.Name         = "Speech service speak";
            speechThread.IsBackground = true;
            speechThread.Start();
            if (wait)
            {
                speechThread.Join();
            }
        }
        public void Speak(string script, string voice, int echoDelay, int distortionLevel, int chorusLevel, int reverbLevel, int compressLevel, bool radio)
        {
            if (script == null)
            {
                return;
            }

            try
            {
                using (SpeechSynthesizer synth = new SpeechSynthesizer())
                    using (MemoryStream stream = new MemoryStream())
                    {
                        if (String.IsNullOrWhiteSpace(voice))
                        {
                            voice = configuration.StandardVoice;
                        }
                        if (voice != null)
                        {
                            try
                            {
                                synth.SelectVoice(voice);
                            }
                            catch { }
                        }

                        synth.Rate   = configuration.Rate;
                        synth.Volume = configuration.Volume;

                        synth.SetOutputToWaveStream(stream);
                        string speech = SpeechFromScript(script);
                        if (speech.Contains("<phoneme"))
                        {
                            speech = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xml:lang=\"" + locale + "\"><s>" + speech + "</s></speak>";
                            synth.SpeakSsml(speech);
                        }
                        else
                        {
                            synth.Speak(speech);
                        }
                        stream.Seek(0, SeekOrigin.Begin);

                        using (System.IO.StreamWriter file = new System.IO.StreamWriter(Environment.GetEnvironmentVariable("AppData") + @"\EDDI\speech.log", true)) { file.WriteLine("" + System.Threading.Thread.CurrentThread.ManagedThreadId + ": Turned script " + script + " in to speech " + speech); }

                        IWaveSource source = new WaveFileReader(stream);

                        // We need to extend the duration of the wave source if we have any effects going on
                        if (chorusLevel != 0 || reverbLevel != 0 || echoDelay != 0)
                        {
                            // Add a base of 500ms plus 10ms per effect level over 50
                            source = source.AppendSource(x => new ExtendedDurationWaveSource(x, 500 + Math.Max(0, (configuration.EffectsLevel - 50) * 10)));
                        }

                        // Add various effects...

                        // We always have chorus
                        if (chorusLevel != 0)
                        {
                            source = source.AppendSource(x => new DmoChorusEffect(x)
                            {
                                Depth = chorusLevel, WetDryMix = Math.Min(100, (int)(180 * ((decimal)configuration.EffectsLevel) / ((decimal)100))), Delay = 16, Frequency = 2, Feedback = 25
                            });
                        }

                        // We only have reverb and echo if we're not transmitting or receiving
                        if (!radio)
                        {
                            if (reverbLevel != 0)
                            {
                                // We tone down the reverb level with the distortion level, as the combination is nasty
                                source = source.AppendSource(x => new DmoWavesReverbEffect(x)
                                {
                                    ReverbTime = (int)(1 + 999 * ((decimal)configuration.EffectsLevel) / ((decimal)100)), ReverbMix = Math.Max(-96, -96 + (96 * reverbLevel / 100) - distortionLevel)
                                });
                            }

                            if (echoDelay != 0)
                            {
                                // We tone down the echo level with the distortion level, as the combination is nasty
                                source = source.AppendSource(x => new DmoEchoEffect(x)
                                {
                                    LeftDelay = echoDelay, RightDelay = echoDelay, WetDryMix = Math.Max(5, (int)(10 * ((decimal)configuration.EffectsLevel) / ((decimal)100)) - distortionLevel), Feedback = Math.Max(0, 10 - distortionLevel / 2)
                                });
                            }
                        }

                        if (configuration.EffectsLevel > 0 && distortionLevel > 0)
                        {
                            source = source.AppendSource(x => new DmoDistortionEffect(x)
                            {
                                Edge = distortionLevel, Gain = -6 - (distortionLevel / 2), PostEQBandwidth = 4000, PostEQCenterFrequency = 4000
                            });
                        }

                        if (radio)
                        {
                            source = source.AppendSource(x => new DmoDistortionEffect(x)
                            {
                                Edge = 7, Gain = -4 - distortionLevel / 2, PostEQBandwidth = 2000, PostEQCenterFrequency = 6000
                            });
                            source = source.AppendSource(x => new DmoCompressorEffect(x)
                            {
                                Attack = 1, Ratio = 3, Threshold = -10
                            });
                        }

                        EventWaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
                        var             soundOut   = new WasapiOut();
                        soundOut.Initialize(source);
                        soundOut.Stopped += (s, e) => waitHandle.Set();

                        activeSpeeches.Add(soundOut);
                        soundOut.Play();

                        // Add a timeout, in case it doesn't come back
                        waitHandle.WaitOne(source.GetTime(source.Length));

                        // It's possible that this has been disposed of, so ensure that it's still there before we try to finish it
                        lock (activeSpeeches)
                        {
                            if (activeSpeeches.Contains(soundOut))
                            {
                                activeSpeeches.Remove(soundOut);
                                soundOut.Stop();
                                soundOut.Dispose();
                            }
                        }

                        source.Dispose();
                    }
            }
            catch (Exception ex)
            {
                using (System.IO.StreamWriter file = new System.IO.StreamWriter(Environment.GetEnvironmentVariable("AppData") + @"\EDDI\speech.log", true)) { file.WriteLine("" + System.Threading.Thread.CurrentThread.ManagedThreadId + ": Caught exception " + ex); }
            }
        }
Exemplo n.º 3
0
        public void Speak(string speech, string voice, int echoDelay, int distortionLevel, int chorusLevel, int reverbLevel, int compressLevel, bool wait = true, int priority = 3)
        {
            if (speech == null)
            {
                return;
            }

            Thread speechThread = new Thread(() =>
            {
                string finalSpeech = null;
                try
                {
                    using (SpeechSynthesizer synth = new SpeechSynthesizer())
                        using (MemoryStream stream = new MemoryStream())
                        {
                            if (string.IsNullOrWhiteSpace(voice))
                            {
                                voice = configuration.StandardVoice;
                            }
                            if (voice != null && !voice.Contains("Microsoft Server Speech Text to Speech Voice"))
                            {
                                try
                                {
                                    Logging.Debug("Selecting voice " + voice);
                                    synth.SelectVoice(voice);
                                    Logging.Debug("Selected voice " + synth.Voice.Name);
                                }
                                catch (Exception ex)
                                {
                                    Logging.Error("Failed to select voice " + voice, ex);
                                }
                            }

                            Logging.Debug("Post-selection");
                            Logging.Debug("Configuration is " + configuration == null ? "<null>" : JsonConvert.SerializeObject(configuration));
                            synth.Rate = configuration.Rate;
                            Logging.Debug("Rate is " + synth.Rate);
                            synth.Volume = configuration.Volume;
                            Logging.Debug("Volume is " + synth.Volume);

                            synth.StateChanged += new EventHandler <StateChangedEventArgs>(synth_StateChanged);
                            Logging.Debug("Tracking state changes");
                            synth.SetOutputToWaveStream(stream);
                            Logging.Debug("Output set to stream");
                            if (speech.Contains("<phoneme") || speech.Contains("<break"))
                            {
                                Logging.Debug("Speech is SSML");
                                if (configuration.DisableSsml)
                                {
                                    Logging.Debug("Disabling SSML at user request");
                                    // User has disabled SSML so remove it
                                    finalSpeech = Regex.Replace(speech, "<.*?>", string.Empty);
                                    synth.Speak(finalSpeech);
                                }
                                else
                                {
                                    Logging.Debug("Obtaining best guess culture");
                                    string culture = bestGuessCulture(synth);
                                    Logging.Debug("Best guess culture is " + culture);
                                    finalSpeech = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xml:lang=\"" + bestGuessCulture(synth) + "\"><s>" + speech + "</s></speak>";
                                    Logging.Debug("SSML speech: " + finalSpeech);
                                    try
                                    {
                                        Logging.Debug("Speaking SSML");
                                        synth.SpeakSsml(finalSpeech);
                                        Logging.Debug("Finished speaking SSML");
                                    }
                                    catch (Exception ex)
                                    {
                                        Logging.Error("Best guess culture of " + bestGuessCulture(synth) + " for voice " + synth.Voice.Name + " was incorrect", ex);
                                        Logging.Info("SSML does not work for the chosen voice; falling back to normal speech");
                                        // Try again without Ssml
                                        finalSpeech = Regex.Replace(speech, "<.*?>", string.Empty);
                                        synth.Speak(finalSpeech);
                                    }
                                }
                            }
                            else
                            {
                                Logging.Debug("Speech does not contain SSML");
                                Logging.Debug("Speech: " + speech);
                                finalSpeech = speech;
                                Logging.Debug("Speaking normal speech");
                                synth.Speak(finalSpeech);
                                Logging.Debug("Finished speaking normal speech");
                            }
                            Logging.Debug("Seeking back to the beginning of the stream");
                            stream.Seek(0, SeekOrigin.Begin);

                            Logging.Debug("Setting up source from stream");
                            IWaveSource source = new WaveFileReader(stream);

                            // We need to extend the duration of the wave source if we have any effects going on
                            if (chorusLevel != 0 || reverbLevel != 0 || echoDelay != 0)
                            {
                                // Add a base of 500ms plus 10ms per effect level over 50
                                Logging.Debug("Extending duration by " + 500 + Math.Max(0, (configuration.EffectsLevel - 50) * 10) + "ms");
                                source = source.AppendSource(x => new ExtendedDurationWaveSource(x, 500 + Math.Max(0, (configuration.EffectsLevel - 50) * 10)));
                            }

                            // Add various effects...
                            Logging.Debug("Effects level is " + configuration.EffectsLevel + ", chorus level is " + chorusLevel + ", reverb level is " + reverbLevel + ", echo delay is " + echoDelay);
                            // We always have chorus
                            if (chorusLevel != 0)
                            {
                                Logging.Debug("Adding chorus");
                                source = source.AppendSource(x => new DmoChorusEffect(x)
                                {
                                    Depth = chorusLevel, WetDryMix = Math.Min(100, (int)(180 * ((decimal)configuration.EffectsLevel) / ((decimal)100))), Delay = 16, Frequency = (configuration.EffectsLevel / 10), Feedback = 25
                                });
                            }

                            // We only have reverb and echo if we're not transmitting or receiving
                            //if (!radio)
                            //{
                            if (reverbLevel != 0)
                            {
                                Logging.Debug("Adding reverb");
                                // We tone down the reverb level with the distortion level, as the combination is nasty
                                source = source.AppendSource(x => new DmoWavesReverbEffect(x)
                                {
                                    ReverbTime = (int)(1 + 999 * ((decimal)configuration.EffectsLevel) / ((decimal)100)), ReverbMix = Math.Max(-96, -96 + (96 * reverbLevel / 100) - distortionLevel)
                                });
                            }

                            if (echoDelay != 0)
                            {
                                Logging.Debug("Adding echo");
                                // We tone down the echo level with the distortion level, as the combination is nasty
                                source = source.AppendSource(x => new DmoEchoEffect(x)
                                {
                                    LeftDelay = echoDelay, RightDelay = echoDelay, WetDryMix = Math.Max(5, (int)(10 * ((decimal)configuration.EffectsLevel) / ((decimal)100)) - distortionLevel), Feedback = Math.Max(0, 10 - distortionLevel / 2)
                                });
                            }
                            //}

                            if (configuration.EffectsLevel > 0 && distortionLevel > 0)
                            {
                                Logging.Debug("Adding distortion");
                                source = source.AppendSource(x => new DmoDistortionEffect(x)
                                {
                                    Edge = distortionLevel, Gain = -distortionLevel / 2, PostEQBandwidth = 4000, PostEQCenterFrequency = 4000
                                });
                            }

                            //if (radio)
                            //{
                            //    source = source.AppendSource(x => new DmoDistortionEffect(x) { Edge = 7, Gain = -distortionLevel / 2, PostEQBandwidth = 2000, PostEQCenterFrequency = 6000 });
                            //    source = source.AppendSource(x => new DmoCompressorEffect(x) { Attack = 1, Ratio = 3, Threshold = -10 });
                            //}

                            if (priority < activeSpeechPriority)
                            {
                                Logging.Debug("About to StopCurrentSpeech");
                                StopCurrentSpeech();
                                Logging.Debug("Finished StopCurrentSpeech");
                            }

                            Logging.Debug("Creating waitHandle");
                            EventWaitHandle waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
                            Logging.Debug("Setting up soundOut");
                            var soundOut = GetSoundOut();
                            Logging.Debug("Setting up soundOut");
                            soundOut.Initialize(source);
                            Logging.Debug("Configuring waitHandle");
                            soundOut.Stopped += (s, e) => waitHandle.Set();

                            Logging.Debug("Starting speech");
                            StartSpeech(soundOut, priority);

                            Logging.Debug("Waiting for speech");
                            // Add a timeout, in case it doesn't come back with the signal
                            waitHandle.WaitOne(source.GetTime(source.Length));
                            Logging.Debug("Finished waiting for speech");

                            Logging.Debug("Stopping speech (just to be sure)");
                            StopCurrentSpeech();
                            Logging.Debug("Disposing of speech source");
                            source.Dispose();
                        }
                }
                catch (Exception ex)
                {
                    Logging.Error("Failed to speak \"" + finalSpeech + "\"", ex);
                }
            });

            Logging.Debug("Setting thread name");
            speechThread.Name = "Speech service speak";
            Logging.Debug("Setting thread background");
            speechThread.IsBackground = true;
            try
            {
                Logging.Debug("Starting speech thread");
                speechThread.Start();
                if (wait)
                {
                    Logging.Debug("Waiting for speech thread");
                    speechThread.Join();
                    Logging.Debug("Finished waiting for speech thread");
                }
            }
            catch (ThreadAbortException tax)
            {
                Thread.ResetAbort();
                Logging.Error(speech, tax);
            }
            catch (Exception ex)
            {
                Logging.Error(speech, ex);
                speechThread.Abort();
            }
        }