前回の前提を踏まえ、それでは比較的簡単な音声合成を使うプログラムからやりましょうか。
今回のお題である「音声合成」をwebであさっていたら、2019年のIlia SmirnovさんによるMicrosoft Learnの英文記事(注)
に小ネタが取り上げられており、その4番目のものを、英語+ロシア語から英語+日本語にしてウィンドウプログラムに改造してみました。
注:Speech - Text-To-Speech Synthesis in .NET | Microsoft Learn
また、ウィンドウ関係は今までWPFを学んできたのでその流れでプログラミングしてみます。(但し、Visual Studio+Xamlを使わないので、C#のコードだけでやります。)
プログラムを起動すると、3つのWPFボタンが付いたウィンドウが現れます。
//解説:↑ではマウスが"Exit"ボタンに掛かっているので背景色が変わっています。
英語ボタンを押すと英語で、日本語ボタンを押すと日本語で、(↑のMicrosoft Learnの記事ではフィンランドのVantaa空港での)スペイン行き乗り換え便案内をしゃべります。又、Exitボタンまたは「✖」システムボタンでプログラムを終了させる際にも(私の記憶にある)「(コロナのおかげで)懐かしいセリフ」をしゃべってくれます。
但し、
私的評価では「英語(Zira)は完璧。日本語(Haruka)は乗換案内は(『お進みください』がちょっと危なっかしいが)何とかクリア。しかし、最後のHarukaのセリフ(『誠に』)は完全に訛ってます!!!」です。
【Speech.Synthesizer04.cs】
///////////////////////////
// Speech.Synthesizer04.cs
///////////////////////////
using System;
using System.Windows; //PresentationFramework.dllにある
using System.Windows.Controls; //PresentationFramework.dllにある
using System.Collections.Generic; //解説:IDictionaryというキーと値がペアの配列を使うので
using System.Globalization; //解説:CultureInfoを使うので(何故C++等アンマネージドコードの開発では "ロケール" と呼ぶのでしょう?分からない...)
using System.Speech.Synthesis; //System.Speech.dllにある(Ref:C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Speech\v4.0_4.0.0.0__31bf3856ad364e35\System.Speech.dll)
namespace Speech.Synthesizer04
{
public partial class MainWindow : Window
{
private const string en = "en-US";
//private const string ru = "ru-RU"; //解説:オリジナルは英語とロシア語でした。
private const string jp = "jp-JP"; //解説:日本語を追加しています。
private readonly IDictionary<string, string> _messagesByCulture = new Dictionary<string, string>();
//解説:今回はキーと値のペアが両方とも文字列です。
public MainWindow()
{
this.Title = "SpeechSynthesizer サンプル";
this.Width = 320; //ウィンドウ幅
this.Height = 280; //ウィンドウ高
this.MinWidth = 320; //ウィンドウ最小幅
this.MinHeight = 280; //ウィンドウ最小高
/* ダイアログタイプ */
//this.WindowStyle = WindowStyle.ThreeDBorderWindow;
//this.ResizeMode = ResizeMode.NoResize;
/* サイズ変更ウィンドウタイプ */
this.WindowStyle = WindowStyle.SingleBorderWindow;
this.ResizeMode = ResizeMode.CanResizeWithGrip; //解説:サイズ変更用の「グリップ」が付いています。
this.WindowState = WindowState.Normal;
this.WindowStartupLocation = WindowStartupLocation.CenterScreen; //Manual, CenterOwnerもある
this.Background = SystemColors.ControlBrush;
InitControls();
PopulateMessages();
this.Closing += Window_Closing;
}
public void InitControls()
{
//スタックパネル(StackPanel)を作成
StackPanel stackPanel = new StackPanel();
stackPanel.Width = 300;
stackPanel.Height = 260;
//ボタン1
Button Btn1 = new Button();
Btn1.Width = 240;
Btn1.Height = 60;
Btn1.HorizontalAlignment = HorizontalAlignment.Center;
Btn1.VerticalAlignment = VerticalAlignment.Center;
//Btn1.HorizontalContentAlignment = HorizontalAlignment.Left; //ボタン内のコンテントの位置
//Btn1.VerticalContentAlignment = VerticalAlignment.Bottom; //ボタン内のコンテントの位置
Btn1.Margin = new Thickness(10, 10, 10, 10); //構造体 Thickness(左, 上, 右, 下)
Btn1.Content = "Englich";
Btn1.Click += PromptInEnglish;
stackPanel.Children.Add(Btn1);
//ボタン2ボタン
Button Btn2 = new Button();
Btn2.Width = 240;
Btn2.Height = 60;
Btn2.HorizontalAlignment = HorizontalAlignment.Center;
Btn2.VerticalAlignment = VerticalAlignment.Center;
Btn2.Margin = new Thickness(10, 10, 10, 10); //構造体 Thickness(左, 上, 右, 下)
Btn2.Content = "Japanese"; //解説:もともとはロシア語でしたが...
Btn2.Click += PromptInJapanese;
stackPanel.Children.Add(Btn2);
//終了ボタン
Button extBtn = new Button();
extBtn.Width = 240;
extBtn.Height = 60;
extBtn.HorizontalAlignment = HorizontalAlignment.Center;
extBtn.VerticalAlignment = VerticalAlignment.Center;
extBtn.Margin = new Thickness(10, 10, 10, 10); //構造体 Thickness(左, 上, 右, 下)
extBtn.Content = "Exit";
extBtn.Click += ExitBtn;
stackPanel.Children.Add(extBtn);
//ウィンドウにスタックパネルを登録
this.Content = stackPanel;
}
[STAThread]
public static void Main()
{
MainWindow mw = new MainWindow();
Application ap = new Application(); //WPFでは明示的にインスタンスを作らなければならない
ap.Run(mw);
}
//終了処理
void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
_messagesByCulture[en] = "Thank you for flying with us today.";
DoPrompt(en);
_messagesByCulture[jp] = "ご搭乗、誠に有難うございました。";
DoPrompt(jp);
}
private void PromptInEnglish(object sender, RoutedEventArgs e)
{
DoPrompt(en);
}
/* private void PromptInRussian(object sender, RoutedEventArgs e)
{
DoPrompt(ru); //解説:オリジナルはロシア語でした。
}
*/
private void PromptInJapanese(object sender, RoutedEventArgs e)
{
DoPrompt(jp);
}
private void ExitBtn(object sender, RoutedEventArgs e)
{
Close();
}
private void DoPrompt(string culture)
{
SpeechSynthesizer synthesizer = new SpeechSynthesizer(); //解説:音声合成インスタンスを作ります。
synthesizer.SetOutputToDefaultAudioDevice(); //解説:標準出力を設定します。
//解説:若しなければ致命的エラーになります...汗; ここはusingかtry~catchを使うともっと良いでしょうが。
PromptBuilder builder = new PromptBuilder(); //解説:「プロンプト作成」クラスとは「プロンプト(IT分野では文字や音声で操作情報等を提供するものをいいます)」作成するクラスです。
builder.StartVoice(new CultureInfo(culture)); //解説:カルチャー(理系は「カルチャ」、localeのこと)を設定して、科白作りを始めます。
builder.AppendText(_messagesByCulture[culture]); //解説:あらかじめ用意していた科白を使います。
builder.EndVoice(); //解説:科白の終了です。
synthesizer.Speak(builder); //解説:科白をしゃべらせます。
synthesizer.Dispose(); //解説:オリジナルのプログラムではメモリー開放を明示的に行わず、ガーベージコレクターに任せていましたが、SpeechSynthesizerクラスの注意書きがあったので追加してみました。
}
private void PopulateMessages()
{
_messagesByCulture[en] = "For the connection flight 123 to Saint Petersburg, please, proceed to gate A1";
//_messagesByCulture[ru] = "Для пересадки на рейс 123 в Санкт-Петербург, пожалуйста, пройдите к выходу A1"; //解説:オリジナルのロシア語です。
_messagesByCulture[jp] = "スペインのピーターズバーグへのお乗り継ぎは、A1番ゲートへお進みください。";
}
}
}