OSDN Git Service

RAD-464: Speech plugin improvements, ability to set speech rate, and 3D sound
[radegast/radegast.git] / plugins / Radegast.Plugin.Speech / RadSpeechWin / WinSynth.cs
1 
2 using System;
3 using System.Collections.Generic;
4 using System.Linq;
5 using System.Text;
6 using System.Speech;
7 using System.IO;
8 using System.Speech.Synthesis;
9 using System.Speech.AudioFormat;
10 using RadegastSpeech.Talk;
11 using OpenMetaverse;
12 using OpenMetaverse.StructuredData;
13 using Radegast;
14
15 namespace RadegastSpeech
16 {
17     class WinSynth
18     {
19         private SpeechSynthesizer syn;
20
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;
28         PromptRate voiceRate
29         {
30             get
31             {
32                 if (voiceProperties != null)
33                 {
34                     string voiceSpeed = voiceProperties["voice_speed"];
35                     if (!string.IsNullOrEmpty(voiceSpeed))
36                     {
37                         switch (voiceSpeed)
38                         {
39                             case "fast": return PromptRate.Fast;
40                             case "slow": return PromptRate.Slow;
41                         }
42                     }
43                 }
44                 return PromptRate.Medium;
45             }
46         }
47
48         internal WinSynth(PluginControl pc, string[] beeps)
49         {
50             BeepNames = beeps;
51             voiceProperties = pc.config["properties"] as OSDMap;
52         }
53
54         internal void SpeechStart()
55         {
56             syn = new SpeechSynthesizer();
57         }
58
59         internal void SpeechStop()
60         {
61             syn.Dispose();
62             syn = null;
63         }
64
65         /// <summary>
66         /// Interrupt synthesis immediately.
67         /// </summary>
68         internal void Halt()
69         {
70         }
71
72         internal void Speak(QueuedSpeech utterance, string outputfile)
73         {
74             if (syn == null) return;
75
76             // Got something to say.  Initialize temp file.
77             StartSpeech(utterance.voice, outputfile);
78
79             // Play the beep, if there is one.
80             if (utterance.beep != BeepType.None)
81             {
82                 pb.StartSentence();
83                 pb.AppendAudio(BeepNames[(int)utterance.beep]);
84                 pb.EndSentence();
85             }
86
87             // Say who is talking, if we know.
88             if (utterance.speaker != null)
89             {
90                 if (utterance.isAction)
91                 {
92                     SayPrompt(utterance.speaker);
93                 }
94                 else
95                 {
96                     // Speak the name of the person speaking.  This is done in a neutral,
97                     // softer voice, speaking slightly rapidly.
98                     SayPrompt(utterance.speaker + ".");
99                 }
100             }
101
102             // Then synthesize the main part of the message.
103             SaySegment(utterance);
104
105             // Close the temporary WAV file.
106             FinishSpeech();
107         }
108
109         private void StartSpeech(AssignedVoice vb, string outputfile)
110         {
111             WinAvailableVoice wv = (WinAvailableVoice)vb.root;
112
113             // Find the best audio format to use for this voice.
114             System.Collections.ObjectModel.ReadOnlyCollection<SpeechAudioFormatInfo> formats =
115                 wv.winVoice.VoiceInfo.SupportedAudioFormats;
116
117             format = formats.FirstOrDefault();
118
119             if (format == null)
120             {
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,
125                     AudioChannel.Mono);
126             }
127
128             // First set up to synthesize the message into a WAV file.
129             mstream = new FileStream(outputfile, FileMode.Create, FileAccess.Write);
130
131             syn.SetOutputToWaveStream(mstream);
132
133             pb = new PromptBuilder();
134             mainStyle = new PromptStyle();
135             //            mainStyle.Volume = promptVol;
136             syn.SelectVoice(wv.winVoice.VoiceInfo.Name);
137             pb.StartStyle(mainStyle);
138         }
139
140         /// <summary>
141         /// Say the main part of the message.
142         /// </summary>
143         /// <param name="utterance"></param>
144         private void SaySegment(QueuedSpeech utterance)
145         {
146             PromptStyle body = new PromptStyle();
147             /* User preference now
148             switch (utterance.voice.rateModification)
149             {
150                 case 00: body.Rate = PromptRate.Fast; break;
151                 case +1: body.Rate = PromptRate.Fast; break;
152                 case -1: body.Rate = PromptRate.Medium; break;
153             }
154             */
155
156             body.Rate = voiceRate;
157
158             switch (utterance.voice.pitchModification)
159             {
160                 case 00: body.Emphasis = PromptEmphasis.Moderate; break;
161                 case +1: body.Emphasis = PromptEmphasis.Strong; break;
162                 case -1: body.Emphasis = PromptEmphasis.Reduced; break;
163             }
164
165             pb.StartStyle(body);
166             pb.StartSentence();
167             pb.AppendText(utterance.message);
168             pb.EndSentence();
169             pb.EndStyle();
170         }
171
172         /// <summary>
173         /// Say the introductory tag.
174         /// </summary>
175         /// <param name="text"></param>
176         private void SayPrompt(string text)
177         {
178             PromptStyle intro = new PromptStyle();
179             intro.Rate = voiceRate;
180             //intro.Volume = promptVol - 1;
181             intro.Emphasis = PromptEmphasis.Moderate;
182             pb.StartStyle(intro);
183             pb.StartSentence();
184             pb.AppendText(text + ":");
185             pb.EndSentence();
186             pb.EndStyle();
187         }
188
189         /// <summary>
190         /// Wrap up recording of the speech.
191         /// </summary>
192         private void FinishSpeech()
193         {
194             pb.EndStyle();
195
196             // Actually generate the WAV file now
197             try
198             {
199                 syn.Speak(pb);
200             }
201             catch (Exception e)
202             {
203                 Logger.Log("Synthesizer error: " + e.Message, Helpers.LogLevel.Error);
204             }
205             finally
206             {
207                 // VERY IMPORTANT we set the synthesizer output to null here.
208                 // Otherwise it does not fully close its output stream.
209                 syn.SetOutputToNull();
210                 mstream.Flush();
211                 mstream.Close();
212                 mstream.Dispose();
213             }
214         }
215
216         /// <summary>
217         /// Get the list of available installed voices.
218         /// </summary>
219         /// <returns></returns>
220         internal Dictionary<string, AvailableVoice> GetVoices()
221         {
222             Dictionary<string, AvailableVoice> names = new Dictionary<string, AvailableVoice>();
223             bool AnnaPresent = false;
224             bool SamPresent = false;
225
226             // Query the synthesizer for the voices installed.
227             System.Collections.ObjectModel.ReadOnlyCollection<InstalledVoice> installed
228                 = syn.GetInstalledVoices();
229
230             // Copy that information into a Dictionary we can update.
231             foreach (InstalledVoice v in installed)
232             {
233                 if (v.Enabled)
234                 {
235                     bool skip = false;
236
237                     // Check for additional information about this voice
238                     if (voiceProperties != null)
239                     {
240                         string propString = voiceProperties[v.VoiceInfo.Name].AsString();
241                         if (propString != null)
242                         {
243                             // Properties are a series of blank-separated keywords
244                             string[] props = propString.Split(' ');
245
246                             foreach (string key in props)
247                             {
248                                 switch (key)
249                                 {
250                                     case "ignore":
251                                         skip = true;
252                                         break;
253                                 }
254                             }
255                         }
256                     }
257
258                     // If this voice is not blocked add it to the list.
259                     if (!skip)
260                     {
261                         WinAvailableVoice wav = new WinAvailableVoice(v);
262                         names[v.VoiceInfo.Name] = (AvailableVoice)wav;
263                     }
264                 }
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;
268             }
269
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)
273             {
274                 RemoveIf(names, "Microsoft Sam");
275                 RemoveIf(names, "Microsoft Mike");
276                 RemoveIf(names, "Microsoft Mary");
277             }
278             //says "Blah to Blah Blah the Blah Blah"
279             RemoveIf(names, "SampleTTSVoice");
280
281             return names;
282         }
283
284         private void RemoveIf(Dictionary<string, AvailableVoice> d, string key)
285         {
286             if (d.ContainsKey(key))
287                 d.Remove(key);
288         }
289
290         class WinAvailableVoice : AvailableVoice
291         {
292             internal InstalledVoice winVoice;
293             internal WinAvailableVoice(InstalledVoice i)
294             {
295                 winVoice = i;
296                 Name = i.VoiceInfo.Name;
297                 Male = (winVoice.VoiceInfo.Gender == VoiceGender.Male);
298             }
299         }
300     }
301 }