OSDN Git Service

RAD-464: Speech plugin improvements, ability to set speech rate, and 3D sound
[radegast/radegast.git] / plugins / Radegast.Plugin.Speech / RadSpeech / PluginControl.cs
1 // 
2 // Radegast Metaverse Client Speech Interface
3 // Copyright (c) 2009-2014, Radegast Development Team
4 // All rights reserved.
5 // 
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
8 // 
9 //     * Redistributions of source code must retain the above copyright notice,
10 //       this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above copyright
12 //       notice, this list of conditions and the following disclaimer in the
13 //       documentation and/or other materials provided with the distribution.
14 //     * Neither the name of the application "Radegast", nor the names of its
15 //       contributors may be used to endorse or promote products derived from
16 //       this software without specific prior written permission.
17 // 
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 // $Id: PluginControl.cs 203 2009-09-07 19:26:02Z mojitotech@gmail.com $
30 //
31 using System;
32 using System.Collections.Generic;
33 using System.Linq;
34 using System.Text;
35 using System.IO;
36 using System.Reflection;
37 using Radegast;
38 using System.Windows.Forms;
39 using OpenMetaverse;
40 using OpenMetaverse.StructuredData;
41
42 namespace RadegastSpeech
43 {
44     [Radegast.Plugin(Name = "Speech", Description = "Adds TTS and STT accesibility capabilities to Radegast", Version = "0.3")]
45     public class PluginControl : IRadegastPlugin
46     {
47         private const string VERSION = "0.3";
48         public RadegastInstance instance;
49         internal Talk.Control talker;
50         internal Listen.Control listener;
51         internal Conversation.Control converse;
52         internal Environment.Control env;
53         internal Sound.Control sound;
54         internal ToolStripDropDownButton ToolsMenu;
55         private ToolStripMenuItem SpeechButton;
56         internal IRadSpeech osLayer;
57         public OSDMap config;
58         private bool started;
59
60         /// <summary>
61         /// Plugin start-up entry.
62         /// </summary>
63         /// <param name="inst"></param>
64         /// <remarks>Called by Radegast at start-up</remarks>
65         public void StartPlugin(RadegastInstance inst)
66         {
67             instance = inst;
68
69             // Get configuration settings, and initialize if not found.
70             config = instance.GlobalSettings["plugin.speech"] as OSDMap;
71
72             if (config == null)
73             {
74                 config = new OSDMap();
75                 config["enabled"] = new OSDBoolean(false);
76                 config["voices"] = new OSDMap();
77                 config["properties"] = new OSDMap();
78                 config["substitutions"] = new OSDMap();
79                 instance.GlobalSettings["plugin.speech"] = config;
80             }
81
82             OSDMap props = (OSDMap)config["properties"];
83             if (props["voice_speed"] == "")
84             {
85                 props["voice_speed"] = "medium";
86             }
87
88             #region Buttons on the plugin menu
89             // SpeechButton = new ToolStripMenuItem("Speech", null, OnSpeechMenuButtonClicked);
90             SpeechButton = new ToolStripMenuItem("Speech");
91             instance.MainForm.PluginsMenu.DropDownItems.Add(SpeechButton);
92
93             SpeechButton.Checked = config["enabled"].AsBoolean();
94
95             // Enabled sub menu
96             {
97                 ToolStripMenuItem button = new ToolStripMenuItem("Enabled", null, (sender, e) =>
98                 {
99                     OnSpeechMenuButtonClicked(SpeechButton, EventArgs.Empty);
100                     ((ToolStripMenuItem)sender).Checked = SpeechButton.Checked;
101                 });
102
103                 button.Checked = SpeechButton.Checked;
104
105                 SpeechButton.DropDownItems.Add(button);
106             }
107
108             SpeechButton.DropDownItems.Add(new ToolStripSeparator());
109
110             // Voice rate
111             {
112                 ToolStripMenuItem slowButton = new ToolStripMenuItem("Slow");
113                 if (props["voice_speed"] == "slow") slowButton.Checked = true;
114                 ToolStripMenuItem mediumButton = new ToolStripMenuItem("Medium");
115                 if (props["voice_speed"] == "medium") mediumButton.Checked = true;
116                 ToolStripMenuItem fastButton = new ToolStripMenuItem("Fast");
117                 if (props["voice_speed"] == "fast") fastButton.Checked = true;
118
119                 slowButton.Click += (sender, e) =>
120                 {
121                     slowButton.Checked = !slowButton.Checked;
122                     if (slowButton.Checked)
123                     {
124                         props["voice_speed"] = "slow";
125                         mediumButton.Checked = false;
126                         fastButton.Checked = false;
127                     }
128                 };
129
130                 mediumButton.Click += (sender, e) =>
131                 {
132                     mediumButton.Checked = !mediumButton.Checked;
133                     if (mediumButton.Checked)
134                     {
135                         props["voice_speed"] = "medium";
136                         slowButton.Checked = false;
137                         fastButton.Checked = false;
138                     }
139                 };
140
141                 fastButton.Click += (sender, e) =>
142                 {
143                     fastButton.Checked = !fastButton.Checked;
144                     if (fastButton.Checked)
145                     {
146                         props["voice_speed"] = "fast";
147                         slowButton.Checked = false;
148                         mediumButton.Checked = false;
149                     }
150                 };
151
152                 SpeechButton.DropDownItems.Add(slowButton);
153                 SpeechButton.DropDownItems.Add(mediumButton);
154                 SpeechButton.DropDownItems.Add(fastButton);
155             }
156
157             SpeechButton.DropDownItems.Add(new ToolStripSeparator());
158
159             // 3D Sound sub menu
160             {
161                 ToolStripMenuItem button = new ToolStripMenuItem("3D Sound", null, (sender, e) =>
162                 {
163                     var me = (ToolStripMenuItem)sender;
164                     me.Checked = !me.Checked;
165                     config["3d_sound"] = me.Checked;
166                     instance.GlobalSettings.Save();
167                     Radegast.Media.Speech.Surround = me.Checked;
168                 });
169
170                 button.Checked = config["3d_sound"].AsBoolean();
171                 Radegast.Media.Speech.Surround = button.Checked;
172
173                 SpeechButton.DropDownItems.Add(button);
174             }
175             #endregion Buttons on the plugin menu
176
177             if (SpeechButton.Checked)
178             {
179                 Initialize();
180             }
181
182             instance.GlobalSettings.Save();
183         }
184
185         /// <summary>
186         /// Startup code (executed only when needed)
187         /// </summary>
188         private void Initialize()
189         {
190             // Never initialize twice
191             if (started) return;
192
193             // Do the one-time only initializations.
194             try
195             {
196                 LoadOSLayer();
197                 env = new Environment.Control(this);
198                 talker = new Talk.Control(this);
199                 listener = new Listen.Control(this);
200                 converse = new Conversation.Control(this);
201                 sound = new Sound.FmodSound(this);
202                 StartControls();
203                 if (!instance.Netcom.IsLoggedIn)
204                 {
205                     talker.SayMore("Press enter to connect.");
206                 }
207                 else
208                 {
209                     // Create conversations and pick active one if we are activating
210                     // the speech plugin mid-session
211                     foreach (RadegastTab tab in instance.TabConsole.Tabs.Values)
212                     {
213                         converse.CreateConversationFromTab(tab, false);
214                     }
215                     converse.ActivateConversationFromTab(instance.TabConsole.SelectedTab);
216
217                 }
218                 started = true;
219             }
220             catch (Exception e)
221             {
222                 SpeechButton.Checked = false;
223                 config["enabled"] = OSD.FromBoolean(false);
224                 SaveSpeechSettings();
225                 System.Windows.Forms.MessageBox.Show("Speech failed initialization: " + e.Message);
226                 return;
227             }
228         }
229
230         /// <summary>
231         /// Shutdown speech module
232         /// </summary>
233         private void Shutdown()
234         {
235             if (!started) return;
236             try
237             {
238                 converse.Shutdown();
239                 listener.Shutdown();
240                 talker.Shutdown();
241                 env.Shutdown();
242                 sound.Shutdown();
243             }
244             catch (Exception e)
245             {
246                 Logger.Log("Failed to shutdown speech modules: ", Helpers.LogLevel.Warning, e);
247             }
248             finally
249             {
250                 started = false;
251             }
252         }
253
254         /// <summary>
255         /// Start all speech subsystems 
256         /// </summary>
257         private void StartControls()
258         {
259             // Start up each of the area controllers.  The order
260             // here is important.
261             try
262             {
263                 sound.Start();      // Sound output
264                 talker.Start();     // Synthesis
265                 listener.Start();   // Recognition
266                 converse.Start();   // Topic-specific conversations
267                 env.Start();        // Environmental awareness
268             }
269             catch (Exception e)
270             {
271                 System.Windows.Forms.MessageBox.Show("Speech can not start.  See log.");
272                 Logger.Log("Speech can not start.", Helpers.LogLevel.Error, e);
273
274                 System.Console.WriteLine(e.StackTrace);
275                 MarkDisabled();
276                 return;
277             }
278
279             // Register the speech-related actions for context menus.
280             // Editing voice assignments to avatars.
281             instance.ContextActionManager.RegisterContextAction(
282                 new GUI.AvatarSpeechAction(instance, this));
283             // Reading the contents of notecards.
284             instance.ContextActionManager.RegisterContextAction(
285                 new GUI.NotecardReadAction(instance, this));
286
287             talker.Say("Rahdegast is ready.");
288         }
289
290         void MarkDisabled()
291         {
292             SpeechButton.Checked = false;
293             osLayer = null;
294         }
295
296         /// <summary>
297         /// Plugin shut-down entry
298         /// </summary>
299         /// <param name="inst"></param>
300         /// <remarks>Called by Radegast at shut-down, or when Speech is switched off.
301         /// We use this to release system resources.</remarks>
302         public void StopPlugin(RadegastInstance inst)
303         {
304             SpeechButton.Dispose();
305             Shutdown();
306         }
307
308         /// <summary>
309         /// Handle toggling of our enable flag
310         /// </summary>
311         /// <param name="sender"></param>
312         /// <param name="e"></param>
313         void OnSpeechMenuButtonClicked(object sender, EventArgs e)
314         {
315             SpeechButton.Checked = !SpeechButton.Checked;
316
317             if (SpeechButton.Checked)
318             {
319                 Initialize();
320             }
321             else
322             {
323                 Shutdown();
324             }
325
326             // Save this into the INI file.
327             config["enabled"] = OSD.FromBoolean(SpeechButton.Checked);
328             SaveSpeechSettings();
329         }
330
331         public void SaveSpeechSettings()
332         {
333             instance.GlobalSettings.Save();
334         }
335
336         /// <summary>
337         /// Find the system-specific DLL for this platform
338         /// </summary>
339         private void LoadOSLayer()
340         {
341             string dirName = Application.StartupPath;
342
343             if (!Directory.Exists(dirName))
344                 throw new Exception("No startup directory found " + dirName);
345
346             // The filename depends on the platform.
347             System.Version version = System.Environment.OSVersion.Version;
348             string loadfilename = null;
349             switch (System.Environment.OSVersion.Platform)
350             {
351                 case PlatformID.Win32NT:
352                 case PlatformID.Win32Windows:
353                     // XP and later have Speech Synthesis.
354                     // XP=5, Vista=6, W7=7
355                     if (version.Major >= 5)
356                         loadfilename = "RadSpeechWin";
357                     break;
358                 case PlatformID.Unix:
359                     loadfilename = "RadSpeechLin";
360                     break;
361                 case PlatformID.MacOSX:
362                     loadfilename = "RadSpeechMac";
363                     break;
364             }
365
366             // If the name was not set, we do not support this platform
367             if (loadfilename == null)
368                 throw new Exception("Platform not supported for Speech");
369
370             loadfilename = Path.Combine(dirName, loadfilename + ".dll");
371             try
372             {
373                 Assembly assembly = Assembly.LoadFile(loadfilename);
374
375                 // Examine the types exposed by this DLL, looking for ours.
376                 foreach (Type type in assembly.GetTypes())
377                 {
378                     if (typeof(IRadSpeech).IsAssignableFrom(type))
379                     {
380                         foreach (var ci in type.GetConstructors())
381                         {
382                             if (ci.GetParameters().Length > 0) continue;
383                             try
384                             {
385                                 // This is the one.  Instantiate it.
386                                 osLayer = (IRadSpeech)ci.Invoke(new object[0]);
387                                 return;
388                             }
389                             catch (Exception ex)
390                             {
391                                 throw new Exception("ERROR in Speech OS Layer: " + ex.Message);
392                             }
393                         }
394                     }
395                 }
396             }
397             catch (BadImageFormatException)
398             {
399                 // non .NET .dlls
400                 throw new Exception("Speech OS Layer bad format " + loadfilename);
401             }
402             catch (ReflectionTypeLoadException ex)
403             {
404                 // Out of date or dlls missing sub dependencies
405                 throw new Exception("ERROR loading Speech OS Layer " + loadfilename + ", " + ex.Message);
406             }
407             catch (Exception ex)
408             {
409                 throw new Exception("Exception loading Speech OS Layer " + loadfilename + ", " + ex.Message);
410             }
411         }
412
413     }
414 }