3 using System.Collections.Generic;
8 using System.Speech.Synthesis;
9 using System.Speech.AudioFormat;
10 using RadegastSpeech.Talk;
12 using OpenMetaverse.StructuredData;
15 namespace RadegastSpeech
19 private SpeechSynthesizer syn;
21 private SpeechAudioFormatInfo format;
22 private PromptBuilder pb;
23 private PromptStyle mainStyle;
24 private FileStream mstream;
25 private PromptVolume promptVol = PromptVolume.Loud;
26 private string[] BeepNames;
27 private OSDMap voiceProperties;
32 if (voiceProperties != null)
34 string voiceSpeed = voiceProperties["voice_speed"];
35 if (!string.IsNullOrEmpty(voiceSpeed))
39 case "fast": return PromptRate.Fast;
40 case "slow": return PromptRate.Slow;
44 return PromptRate.Medium;
48 internal WinSynth(PluginControl pc, string[] beeps)
51 voiceProperties = pc.config["properties"] as OSDMap;
54 internal void SpeechStart()
56 syn = new SpeechSynthesizer();
59 internal void SpeechStop()
66 /// Interrupt synthesis immediately.
72 internal void Speak(QueuedSpeech utterance, string outputfile)
74 if (syn == null) return;
76 // Got something to say. Initialize temp file.
77 StartSpeech(utterance.voice, outputfile);
79 // Play the beep, if there is one.
80 if (utterance.beep != BeepType.None)
83 pb.AppendAudio(BeepNames[(int)utterance.beep]);
87 // Say who is talking, if we know.
88 if (utterance.speaker != null)
90 if (utterance.isAction)
92 SayPrompt(utterance.speaker);
96 // Speak the name of the person speaking. This is done in a neutral,
97 // softer voice, speaking slightly rapidly.
98 SayPrompt(utterance.speaker + ".");
102 // Then synthesize the main part of the message.
103 SaySegment(utterance);
105 // Close the temporary WAV file.
109 private void StartSpeech(AssignedVoice vb, string outputfile)
111 WinAvailableVoice wv = (WinAvailableVoice)vb.root;
113 // Find the best audio format to use for this voice.
114 System.Collections.ObjectModel.ReadOnlyCollection<SpeechAudioFormatInfo> formats =
115 wv.winVoice.VoiceInfo.SupportedAudioFormats;
117 format = formats.FirstOrDefault();
121 // The voice did not tell us its parameters, so we pick some.
122 format = new SpeechAudioFormatInfo(
123 16000, // Samples per second
124 AudioBitsPerSample.Sixteen,
128 // First set up to synthesize the message into a WAV file.
129 mstream = new FileStream(outputfile, FileMode.Create, FileAccess.Write);
131 syn.SetOutputToWaveStream(mstream);
133 pb = new PromptBuilder();
134 mainStyle = new PromptStyle();
135 // mainStyle.Volume = promptVol;
136 syn.SelectVoice(wv.winVoice.VoiceInfo.Name);
137 pb.StartStyle(mainStyle);
141 /// Say the main part of the message.
143 /// <param name="utterance"></param>
144 private void SaySegment(QueuedSpeech utterance)
146 PromptStyle body = new PromptStyle();
147 /* User preference now
148 switch (utterance.voice.rateModification)
150 case 00: body.Rate = PromptRate.Fast; break;
151 case +1: body.Rate = PromptRate.Fast; break;
152 case -1: body.Rate = PromptRate.Medium; break;
156 body.Rate = voiceRate;
158 switch (utterance.voice.pitchModification)
160 case 00: body.Emphasis = PromptEmphasis.Moderate; break;
161 case +1: body.Emphasis = PromptEmphasis.Strong; break;
162 case -1: body.Emphasis = PromptEmphasis.Reduced; break;
167 pb.AppendText(utterance.message);
173 /// Say the introductory tag.
175 /// <param name="text"></param>
176 private void SayPrompt(string text)
178 PromptStyle intro = new PromptStyle();
179 intro.Rate = voiceRate;
180 //intro.Volume = promptVol - 1;
181 intro.Emphasis = PromptEmphasis.Moderate;
182 pb.StartStyle(intro);
184 pb.AppendText(text + ":");
190 /// Wrap up recording of the speech.
192 private void FinishSpeech()
196 // Actually generate the WAV file now
203 Logger.Log("Synthesizer error: " + e.Message, Helpers.LogLevel.Error);
207 // VERY IMPORTANT we set the synthesizer output to null here.
208 // Otherwise it does not fully close its output stream.
209 syn.SetOutputToNull();
217 /// Get the list of available installed voices.
219 /// <returns></returns>
220 internal Dictionary<string, AvailableVoice> GetVoices()
222 Dictionary<string, AvailableVoice> names = new Dictionary<string, AvailableVoice>();
223 bool AnnaPresent = false;
224 bool SamPresent = false;
226 // Query the synthesizer for the voices installed.
227 System.Collections.ObjectModel.ReadOnlyCollection<InstalledVoice> installed
228 = syn.GetInstalledVoices();
230 // Copy that information into a Dictionary we can update.
231 foreach (InstalledVoice v in installed)
237 // Check for additional information about this voice
238 if (voiceProperties != null)
240 string propString = voiceProperties[v.VoiceInfo.Name].AsString();
241 if (propString != null)
243 // Properties are a series of blank-separated keywords
244 string[] props = propString.Split(' ');
246 foreach (string key in props)
258 // If this voice is not blocked add it to the list.
261 WinAvailableVoice wav = new WinAvailableVoice(v);
262 names[v.VoiceInfo.Name] = (AvailableVoice)wav;
265 // Notice certain Microsoft voices.
266 if (v.VoiceInfo.Name.Equals("Microsoft Anna")) AnnaPresent = true;
267 else if (v.VoiceInfo.Name.StartsWith("Microsoft ")) SamPresent = true;
270 // We have all the voices. Remove the old Microsoft voices
271 // if this is Vista or later. This is because they do not work.
272 if (AnnaPresent && SamPresent)
274 RemoveIf(names, "Microsoft Sam");
275 RemoveIf(names, "Microsoft Mike");
276 RemoveIf(names, "Microsoft Mary");
278 //says "Blah to Blah Blah the Blah Blah"
279 RemoveIf(names, "SampleTTSVoice");
284 private void RemoveIf(Dictionary<string, AvailableVoice> d, string key)
286 if (d.ContainsKey(key))
290 class WinAvailableVoice : AvailableVoice
292 internal InstalledVoice winVoice;
293 internal WinAvailableVoice(InstalledVoice i)
296 Name = i.VoiceInfo.Name;
297 Male = (winVoice.VoiceInfo.Gender == VoiceGender.Male);