├── .gitignore ├── LICENSE ├── README.md └── src ├── Speech ├── Controller │ ├── AITalk3Controller.cs │ ├── AITalk3Enumerator.cs │ ├── AIVOICEController.cs │ ├── AIVOICEEnumerator.cs │ ├── COEIROINKController.cs │ ├── COEIROINKEnumerator.cs │ ├── CeVIO64Controller.cs │ ├── CeVIO64Enumerator.cs │ ├── CeVIOAIController.cs │ ├── CeVIOAIEnumerator.cs │ ├── CeVIOController.cs │ ├── CeVIOEnumerator.cs │ ├── GynoidTalkController.cs │ ├── GynoidTalkEnumerator.cs │ ├── ISpeechController.cs │ ├── ISpeechEnumerator.cs │ ├── OtomachiUnaTalkController.cs │ ├── OtomachiUnaTalkEnumerator.cs │ ├── SAPI5Controller.cs │ ├── SAPI5Enumerator.cs │ ├── SHAREVOXController.cs │ ├── SHAREVOXEnumerator.cs │ ├── VOICEPEAKController.cs │ ├── VOICEPEAKEnumerator.cs │ ├── VOICEVOXController.cs │ ├── VOICEVOXEnumerator.cs │ ├── Voiceroid2Controller.cs │ ├── Voiceroid2Enumerator.cs │ ├── Voiceroid64Controller.cs │ ├── Voiceroid64Enumerator.cs │ ├── VoiceroidPlusController.cs │ └── VoiceroidPlusEnumerator.cs ├── Effect │ ├── Wave.cs │ └── Whisper.cs ├── EngineParameters.cs ├── Properties │ └── AssemblyInfo.cs ├── SoundPlayer.cs ├── SoundRecorder.cs ├── Speech.csproj ├── SpeechController.cs ├── SpeechEngineInfo.cs ├── app.config └── packages.config ├── SpeechSample ├── App.config ├── Options.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── SpeechSample.csproj ├── SpeechWebServer ├── App.config ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── SpeechWebServer.csproj ├── app.manifest ├── html │ └── index.html └── packages.config ├── TTSController.sln └── TTSController_vs2019.sln /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | *.db 77 | *.opendb 78 | 79 | # Visual Studio profiler 80 | *.psess 81 | *.vsp 82 | *.vspx 83 | 84 | # TFS 2012 Local Workspace 85 | $tf/ 86 | 87 | # Guidance Automation Toolkit 88 | *.gpState 89 | 90 | # ReSharper is a .NET coding add-in 91 | _ReSharper*/ 92 | *.[Rr]e[Ss]harper 93 | *.DotSettings.user 94 | 95 | # JustCode is a .NET coding addin-in 96 | .JustCode 97 | 98 | # TeamCity is a build add-in 99 | _TeamCity* 100 | 101 | # DotCover is a Code Coverage Tool 102 | *.dotCover 103 | 104 | # NCrunch 105 | _NCrunch_* 106 | .*crunch*.local.xml 107 | 108 | # MightyMoose 109 | *.mm.* 110 | AutoTest.Net/ 111 | 112 | # Web workbench (sass) 113 | .sass-cache/ 114 | 115 | # Installshield output folder 116 | [Ee]xpress/ 117 | 118 | # DocProject is a documentation generator add-in 119 | DocProject/buildhelp/ 120 | DocProject/Help/*.HxT 121 | DocProject/Help/*.HxC 122 | DocProject/Help/*.hhc 123 | DocProject/Help/*.hhk 124 | DocProject/Help/*.hhp 125 | DocProject/Help/Html2 126 | DocProject/Help/html 127 | 128 | # Click-Once directory 129 | publish/ 130 | 131 | # Publish Web Output 132 | *.[Pp]ublish.xml 133 | *.azurePubxml 134 | # TODO: Comment the next line if you want to checkin your web deploy settings 135 | # but database connection strings (with potential passwords) will be unencrypted 136 | *.pubxml 137 | *.publishproj 138 | 139 | # NuGet Packages 140 | *.nupkg 141 | # The packages folder can be ignored because of Package Restore 142 | **/packages/* 143 | # except build/, which is used as an MSBuild target. 144 | !**/packages/build/ 145 | # Uncomment if necessary however generally it will be regenerated when needed 146 | #!**/packages/repositories.config 147 | 148 | # Windows Azure Build Output 149 | csx/ 150 | *.build.csdef 151 | 152 | # Windows Store app package directory 153 | AppPackages/ 154 | 155 | # Others 156 | *.[Cc]ache 157 | ClientBin/ 158 | [Ss]tyle[Cc]op.* 159 | ~$* 160 | *~ 161 | *.dbmdl 162 | *.dbproj.schemaview 163 | *.pfx 164 | *.publishsettings 165 | node_modules/ 166 | bower_components/ 167 | 168 | # RIA/Silverlight projects 169 | Generated_Code/ 170 | 171 | # Backup & report files from converting an old project file 172 | # to a newer Visual Studio version. Backup files are not needed, 173 | # because we have git ;-) 174 | _UpgradeReport_Files/ 175 | Backup*/ 176 | UpgradeLog*.XML 177 | UpgradeLog*.htm 178 | 179 | # SQL Server files 180 | *.mdf 181 | *.ldf 182 | 183 | # Business Intelligence projects 184 | *.rdl.data 185 | *.bim.layout 186 | *.bim_*.settings 187 | 188 | # Microsoft Fakes 189 | FakesAssemblies/ 190 | 191 | # Node.js Tools for Visual Studio 192 | .ntvs_analysis.dat 193 | 194 | # Visual Studio 6 build log 195 | *.plg 196 | 197 | # Visual Studio 6 workspace options file 198 | *.opt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TTSController 2 | 各種 Text-to-Speech エンジンを統一的に操作するライブラリです。VOICEROIDなどを自動化する簡易Webサーバもあります。 3 | 4 | ## 対応プラットフォーム 5 | - Windows 10/11 (64bit) 6 | 7 | ## 対応音声合成ライブラリ 8 | - VOICEROID+ 各種 9 | - 音街ウナTalkEx 10 | - VOICEROID2 各種 (x86, x64) 11 | - A.I.VOICE (要x64ビルド) 12 | - ガイノイドTALK 各種 13 | - かんたん!AITalk3 / 関西風 / Lite 14 | - CeVIO CS6 / CS7 (x86, x64) 15 | - CeVIO AI (要x64ビルド) 16 | - VOICEVOX 17 | - COEIROINK 18 | - SAPI5 (Windows10標準の音声合成機能。スタートメニュー>設定>時刻と言語>音声認識>音声の管理>音声の追加から各国語の音声が追加できます。API仕様により追加しても列挙されない音声があります。) 19 | - VOICEPEAK 20 | 21 | ### 動作確認済みリスト 22 | ライブラリ名はインストールされたフォルダなどを参照して機械的に抽出しているため、リストにないものでも音声合成エンジンが共通であれば動作する可能性が高いです。 23 | |音声合成エンジン|ライブラリ名| 24 | |---|---| 25 | |VOICEROID+ EX|民安ともえ EX, 東北ずん子, 東北きりたん, 京町セイカ| 26 | |音街ウナTalkEx|音街ウナ| 27 | |VOICEROID2|琴葉 茜, 琴葉 葵, 結月ゆかり, 紲星あかり, 東北イタコ, 桜乃そら, ついなちゃん(標準語), ついなちゃん(関西弁)| 28 | |VOICEROID2 (VOICEROID+ EX からのアップグレード)|民安ともえ(v1), 東北ずん子(v1), 東北きりたん(v1), 京町セイカ(v1)| 29 | |かんたん!AITalk3|あんず, かほ, ななこ, のぞみ, せいじ| 30 | |かんたん!AITalk3 関西風|みやび, やまと| 31 | |かんたん!AITalk3 LITE|あんず(LITE), かほ(LITE), ななこ(LITE), のぞみ(LITE), せいじ(LITE)| 32 | |ガイノイドTALK|鳴花ヒメ, 鳴花ミコト| 33 | |A.I.VOICE|琴葉 茜,琴葉 茜(蕾),琴葉 茜,琴葉 茜(蕾),Kotonoha Akane (English),Kotonoha Aoi (English),結城 香, 足立 レイ,栗田まろん| 34 | |CeVIO CS6 / CS7|さとうささら, すずきつづみ, タカハシ, IA, ONE| 35 | |CeVIO AI|さとうささら, 小春六花, 弦巻マキ (英), 弦巻マキ (日) ※ライセンスエラーが出る場合は [#5](https://github.com/ksasao/TTSController/issues/5) へお知らせください| 36 | |VOICEVOX|四国めたん,ずんだもん,春日部つむぎ,雨晴はう,波音リツ,玄野武宏,白上虎太郎,青山龍星,冥鳴ひまり,九州そら| 37 | |COEIROINK|つくよみちゃん,MANA,おふとんP,ディアちゃん,アルマちゃん| 38 | |SAPI5|Microsoft Haruka Desktop, Microsoft David Desktop, Microsoft Zira Desktop, Microsoft Irina Desktop| 39 | |VOICEPEAK(1.2.1以降)|Frimomen, Tohoku Zunko, Zundamon, Japanese Female Child, Japanese Male 1, Japanese Male 2, Japanese Male 3, Japanese Female 1, Japanese Female 2, Japanese Female 3| 40 | 41 | ## ブラウザで音声合成する 42 | この実装は簡易実装であり、音声合成ライブラリと同一のPC上で実行することを想定しています。インターネット上への公開は、セキュリティ上のリスクや音声合成ライブラリのライセンス上の問題がある可能性があります。 43 | 44 | - [ビルド済み実行ファイル64bit版(v0.1.0)](https://github.com/ksasao/TTSController/releases/download/v0.1.0/SpeechWebServer_x64_v0.1.0.zip) (2022/3/15更新) 45 | - [ビルド済み実行ファイル32bit版(v0.1.0)](https://github.com/ksasao/TTSController/releases/download/v0.1.0/SpeechWebServer_x86_v0.1.0.zip) (2022/3/15更新) 46 | 47 | ### 準備 48 | - SpeechWebServer のプロジェクトを Visual Studio 2019 でビルドして ```SpeechWebServer.exe``` を実行します(管理者権限が必要です) 49 | 50 | ### 利用方法 51 | - ブラウザで http://localhost:1000/ を開くと現在の時刻を発話します 52 | - http://localhost:1000/?text=こんにちは を開くと「こんにちは」と発話します。「こんにちは」の部分は任意の文字列を指定できます 53 | - http://localhost:1000/?text=おはようございます&range=1.2&volume=1.0&pitch=0.8&speed=0.8 のように、音量(volume), 話速(speed), 高さ(pitch), 抑揚(range) を指定できます (かんたん!AITalk3 LITE, CeVIO, SAPI5を除く) 54 | - VOICEROID+ 東北きりたんがインストールされている場合、http://localhost:1000/?name=東北きりたん&text=こんばんは を開くと東北きりたんの声で発話します。他の音声合成エンジンを利用する場合は、アプリ起動時に表示される「インストール済み音声合成ライブラリ」の表記を参考に、適宜 name の引数を変更してください。なお、VOICEVOX, COEIROINK を利用する場合は、あらかじめアプリケーションを起動しておいてください。 55 | - 複数の音声合成エンジンがインストールされており、同一のnameが利用されている環境では、http://localhost:1000/?text=アカネチャンやでー&name=琴葉%20茜&engine=AIVOICE のように engine で区別をします 56 | - http://localhost:1000/?text=おはよう&speaker=和室 のように音声を再生するスピーカー名を指定することができます。カッコ内の文字列を前方一致で検索します。なお、Google Home デバイスは Windows から Bluetoothスピーカーとして接続ができ、任意の名前(「和室」など)を付けることが可能です。 57 | - http://localhost:1000/?text=ささやき声なのだ&name=ずんだもん&whisper=0.02&speed=0.8 のようにwhisperを設定することで、任意の音声をささやき声に変換できます(ささやき声化した音声は whisper.wav として自動的に保存されます)。音声がおかしく聞こえる場合は &volume=0.6 などのオプションを指定して音量を小さくしてみてください。 58 | 59 | ![スピーカー名の表示](https://user-images.githubusercontent.com/179872/103144037-c823f200-4765-11eb-93a3-e202a8621ad2.png) 60 | 61 | ## TODO 62 | 63 | ### 制御機能 64 | - [x] 話者の一覧取得 65 | - [x] 話者に応じたTTS切り替え 66 | - [ ] Bluetooth スピーカーの安定動作のための無音区間挿入 67 | - [ ] 同時起動対応(先に起動しているほうに処理を委譲) 68 | 69 | ### 音声コントロール 70 | - [x] 再生 71 | - [x] 音量の取得・変更 72 | - [x] 話速の取得・変更 73 | - [x] ピッチの取得・変更 74 | - [x] 抑揚の取得・変更 75 | - [x] 発話中の音声停止 76 | - [x] 合成した音声の保存 77 | - [ ] 連続して文字列が入力されたときの対応 78 | - [ ] 音声合成対象の文字列の途中に .wav ファイルを差し込み 79 | - [ ] 音声合成対象の文字列の途中に音声コントロールを埋め込み 80 | - [x] 音声出力デバイス選択 81 | 82 | -------------------------------------------------------------------------------- /src/Speech/Controller/AITalk3Controller.cs: -------------------------------------------------------------------------------- 1 | using Codeer.Friendly; 2 | using Codeer.Friendly.Windows; 3 | using Codeer.Friendly.Windows.Grasp; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Diagnostics; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Runtime.InteropServices; 11 | using System.Text; 12 | using System.Threading; 13 | using System.Threading.Tasks; 14 | using System.Windows.Forms; 15 | using System.Windows.Threading; 16 | 17 | namespace Speech 18 | { 19 | /// 20 | /// AITalk3 操作クラス 21 | /// 22 | public class AITalk3Controller : VoiceroidPlusController 23 | { 24 | System.Timers.Timer _timer; // 状態監視のためのタイマー 25 | bool _playStarting = false; 26 | int _voiceIndex = 0; 27 | 28 | 29 | /// 30 | /// AITalk3 のフルパス 31 | /// 32 | public string AITalk3Path { get; private set; } 33 | 34 | 35 | public AITalk3Controller(SpeechEngineInfo info) : base(info) 36 | { 37 | Info = info; 38 | AITalk3Path = info.EnginePath; 39 | _timer = new System.Timers.Timer(100); 40 | _timer.Elapsed += timer_Elapsed; 41 | 42 | AITalk3Enumerator aitalk3Enumerator = new AITalk3Enumerator(); 43 | var list = aitalk3Enumerator.GetSpeechEngineInfo(); 44 | int count = 0; 45 | string exePath = ""; 46 | for(int i=0; i 83 | /// AITalk3 に入力された文字列を再生します 84 | /// 85 | public override void Play() 86 | { 87 | // 話者選択 88 | WindowControl comboBox = _root.IdentifyFromZIndex(2, 0, 0, 1, 0, 0, 0, 0, 0); 89 | AppVar combo = comboBox.AppVar; 90 | combo["SelectedIndex"](_voiceIndex); 91 | 92 | // 再生ボタンをクリック 93 | WindowControl playButton = _root.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 0, 2); 94 | AppVar button = playButton.AppVar; 95 | string text = (string)button["Text"]().Core; 96 | if(text.Trim() == "再生") 97 | { 98 | button["PerformClick"](); 99 | _playStarting = true; 100 | _timer.Start(); 101 | } 102 | } 103 | /// 104 | /// AITalk3 の再生を停止します(停止ボタンを押す) 105 | /// 106 | public override void Stop() 107 | { 108 | WindowControl stopButton = _root.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 0, 1); 109 | AppVar button = stopButton.AppVar; 110 | button["PerformClick"](); 111 | } 112 | 113 | 114 | protected override void SetEffect(EffectType t, float value) 115 | { 116 | if (Info.LibraryName.IndexOf("LITE") > 0) 117 | { 118 | // LITEは各種操作が出来ない 119 | return; 120 | } 121 | ChangeToVoiceEffect(); 122 | int index = (int)t; 123 | WindowControl control = _root.IdentifyFromZIndex(2, 0, 0, 1, 0, 0, 1, 0, 0, index); 124 | AppVar v = control.AppVar; 125 | v["Focus"](); 126 | v["Text"](string.Format("{0:0.00}", value)); 127 | 128 | Thread.Sleep(100); 129 | SendKeys.SendWait("{TAB}"); 130 | } 131 | protected override float GetEffect(EffectType t) 132 | { 133 | if (Info.LibraryName.IndexOf("LITE") > 0) 134 | { 135 | // LITEは各種操作が出来ない 136 | return 1.0f; 137 | } 138 | ChangeToVoiceEffect(); 139 | int index = (int)t; 140 | WindowControl control = _root.IdentifyFromZIndex(2, 0, 0, 1, 0, 0, 1, 0, 0, index); 141 | AppVar v = control.AppVar; 142 | return Convert.ToSingle((string)v["Text"]().Core); 143 | } 144 | 145 | 146 | /// 147 | /// 音声効果タブを選択します 148 | /// 149 | protected override void ChangeToVoiceEffect() 150 | { 151 | RestoreMinimizedWindow(); 152 | WindowControl tabControl = _root.IdentifyFromZIndex(2, 0, 0, 1, 0, 0, 1); 153 | tabControl.SetFocus(); 154 | AppVar tab = tabControl.AppVar; 155 | tab["SelectedIndex"]((int)0); 156 | } 157 | 158 | } 159 | } -------------------------------------------------------------------------------- /src/Speech/Controller/AITalk3Enumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Xml.Linq; 8 | 9 | namespace Speech 10 | { 11 | 12 | public class AITalk3Enumerator : VoiceroidPlusEnumerator 13 | { 14 | 15 | public AITalk3Enumerator() : base() 16 | { 17 | Initialize(); 18 | } 19 | 20 | private void Initialize() 21 | { 22 | EngineName = "AITalk3"; 23 | List voiceData = new List(); 24 | string basePath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + @"\AI\AITalk3"; 25 | if (Directory.Exists(basePath)) 26 | { 27 | string[] dirs = Directory.GetDirectories(basePath); 28 | foreach (var d in dirs) 29 | { 30 | voiceData.AddRange(FindAITalk(d)); 31 | } 32 | } 33 | _info = voiceData.ToArray(); 34 | } 35 | 36 | private List FindAITalk(string path) 37 | { 38 | List data = new List(); 39 | try 40 | { 41 | string[] dirs = Directory.GetDirectories(Path.Combine(path, "voice")); 42 | Array.Sort(dirs); // AITalkの話者の表示順序は英語フォルダ名の辞書式順序 43 | 44 | for (int i = 0; i < dirs.Length; i++) 45 | { 46 | string xmlFile = Path.Combine(dirs[i], "dbconf.xml"); 47 | if (File.Exists(xmlFile)) 48 | { 49 | Data d = new Data(); 50 | var xml = XElement.Load(Path.Combine(xmlFile)); 51 | d.Name = xml.Element("profile").Attribute("name").Value; 52 | if (path.IndexOf("AITalkLite") > 0) 53 | { 54 | d.Name += "(LITE)"; 55 | } 56 | d.Path = Directory.GetFiles(path,"*.exe")[0]; 57 | data.Add(d); 58 | } 59 | } 60 | } 61 | catch 62 | { 63 | // 初期化に途中で失敗した場合はうまく処理できたところまで返す 64 | } 65 | 66 | return data; 67 | } 68 | public override ISpeechController GetControllerInstance(SpeechEngineInfo info) 69 | { 70 | return EngineName == info.EngineName ? new AITalk3Controller(info) : null; 71 | } 72 | 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Speech/Controller/AIVOICEController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Runtime.InteropServices; 9 | using System.Runtime.Serialization.Json; 10 | using System.Text; 11 | using System.Threading; 12 | using System.Threading.Tasks; 13 | 14 | namespace Speech 15 | { 16 | public class AIVOICEController : MarshalByRefObject, IDisposable, ISpeechController 17 | { 18 | public class Master 19 | { 20 | public float Volume { get; set; } = 1; 21 | public float Speed { get; set; } = 1; 22 | public float Pitch { get; set; } = 1; 23 | public float PitchRange { get; set; } = 1; 24 | public bool IsPauseEnabled { get; set; } = true; 25 | public int MiddlePause { get; set; } = 150; 26 | public int LongPause { get; set; } = 370; 27 | public int SentencePause { get; set; } = 800; 28 | public float VolumeDecibel { get; set; } = 0; 29 | public float PitchCent { get; set; } = 0; 30 | public float PitchHalfTone { get; set; } = 0; 31 | public float PitchRangePercent { get; set; } = 100; 32 | } 33 | 34 | Process _process; 35 | System.Timers.Timer _timer; // 状態監視のためのタイマー 36 | Queue _queue = new Queue(); 37 | dynamic _ttsControl = null; 38 | 39 | public delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lparam); 40 | static int _pid = 0; 41 | 42 | public SpeechEngineInfo Info { get; private set; } 43 | 44 | /// 45 | /// A.I.VOICE のフルパス 46 | /// 47 | public string AIVOICEPath { get; private set; } 48 | 49 | string _libraryName; 50 | string _promptString; 51 | bool _isPlaying = false; 52 | public AIVOICEController(SpeechEngineInfo info) 53 | { 54 | Info = info; 55 | 56 | var aivoice = new AIVOICEEnumerator(); 57 | _promptString = aivoice.PromptString; 58 | 59 | AIVOICEPath = info.EnginePath; 60 | _libraryName = info.LibraryName; 61 | _timer = new System.Timers.Timer(100); 62 | _timer.Elapsed += timer_Elapsed; 63 | } 64 | 65 | object _lockObject = new object(); 66 | 67 | private void timer_Elapsed(object sender, EventArgs e) 68 | { 69 | _timer.Stop(); // 途中の処理が重いため、タイマーをいったん止める 70 | if (_queue.Count == 0) 71 | { 72 | StopSpeech(); 73 | return; // タイマーが止まったまま終了 74 | } 75 | else 76 | { 77 | // 喋るべき内容が残っているときは再開 78 | string t = _queue.Dequeue(); 79 | _ttsControl.Text = t; 80 | Play(); 81 | _isPlaying = true; 82 | } 83 | _timer.Start(); 84 | 85 | } 86 | 87 | private void StopSpeech() 88 | { 89 | _timer.Stop(); 90 | lock (_lockObject) 91 | { 92 | if (_isPlaying) 93 | { 94 | _isPlaying = false; 95 | OnFinished(); 96 | } 97 | } 98 | } 99 | 100 | /// 101 | /// 音声再生が完了したときに発生するイベント 102 | /// 103 | public event EventHandler Finished; 104 | protected virtual void OnFinished() 105 | { 106 | EventArgs se = new EventArgs(); 107 | Finished?.Invoke(this, se); 108 | } 109 | 110 | /// 111 | /// A.I.VOICE が起動中かどうかを確認 112 | /// 113 | /// 起動中であれば true 114 | public bool IsActive() 115 | { 116 | string name = Path.GetFileNameWithoutExtension(AIVOICEPath); 117 | Process[] localByName = Process.GetProcessesByName(name); 118 | 119 | if (localByName.Length > 0) 120 | { 121 | // A.I.VOICE は2重起動しないはずなので 0番目を参照する 122 | _process = localByName[0]; 123 | _pid = _process.Id; 124 | return true; 125 | } 126 | return false; 127 | } 128 | 129 | /// 130 | /// A.I.VOICEを起動する。すでに起動している場合には起動しているものを操作対象とする。 131 | /// 132 | public void Activate() 133 | { 134 | string path = Path.Combine(Path.GetDirectoryName(AIVOICEPath), "AI.Talk.Editor.Api.dll"); 135 | Assembly assembly = Assembly.LoadFrom(path); 136 | Type type = assembly.GetType("AI.Talk.Editor.Api.TtsControl"); 137 | _ttsControl = Activator.CreateInstance(type, new object[] { }); 138 | 139 | var names = _ttsControl.GetAvailableHostNames(); 140 | _ttsControl.Initialize(names[0]); // names[0] = "A.I.VOICE Editor" 141 | 142 | if (!IsActive()) 143 | { 144 | _ttsControl.StartHost(); 145 | } 146 | _ttsControl.Connect(); 147 | } 148 | 149 | /// 150 | /// 指定した文字列を再生します 151 | /// 152 | /// 再生する文字列 153 | public void Play(string text) 154 | { 155 | SetText(text); 156 | } 157 | internal void SetText(string text) 158 | { 159 | text = text.Trim() == "" ? "." : text; 160 | string t = _libraryName + _promptString + text; 161 | if (_queue.Count == 0) 162 | { 163 | _ttsControl.Text = t; 164 | Play(); 165 | } 166 | else 167 | { 168 | _queue.Enqueue(t); 169 | } 170 | } 171 | 172 | /// 173 | /// A.I.VOICE に入力された文字列を再生します 174 | /// 175 | public void Play() 176 | { 177 | long ms = _ttsControl.GetPlayTime(); 178 | _ttsControl.Play(); 179 | _isPlaying = true; 180 | _timer.Interval = ms; 181 | _timer.Start(); 182 | } 183 | 184 | private string ConvertToJson(Master master) 185 | { 186 | // この順序の JSON でないと正しくUIに反映されない模様... 187 | return "{ \"Volume\" : " + master.Volume + ", " + 188 | "\"Pitch\" : " + master.Pitch + ", " + 189 | "\"Speed\" : " + master.Speed + ", " + 190 | "\"PitchRange\" : " + master.PitchRange + ", " + 191 | "\"MiddlePause\" : " + master.MiddlePause + ", " + 192 | "\"LongPause\" : " + master.LongPause + ", " + 193 | "\"SentencePause\" : " + master.SentencePause + " }"; 194 | } 195 | private Master ConvertToMaster(string json) 196 | { 197 | var serializer = new DataContractJsonSerializer(typeof(Master)); 198 | using (var mst = new MemoryStream(Encoding.UTF8.GetBytes(json))) 199 | { 200 | return (Master)serializer.ReadObject(mst); 201 | } 202 | } 203 | 204 | private Master GetMaster() 205 | { 206 | var json = _ttsControl.MasterControl; 207 | Master master = ConvertToMaster(json); 208 | return master; 209 | } 210 | 211 | private void SetMaster(Master master) 212 | { 213 | string json = ConvertToJson(master); 214 | _ttsControl.MasterControl = json; 215 | } 216 | 217 | /// 218 | /// A.I.VOICE の再生を停止します 219 | /// 220 | public void Stop() 221 | { 222 | StopSpeech(); 223 | _ttsControl.Stop(); 224 | } 225 | 226 | enum EffectType { Volume = 0, Speed = 1, Pitch = 2, PitchRange = 3 } 227 | /// 228 | /// 音量を設定します 229 | /// 230 | /// 0.0~2.0 231 | public void SetVolume(float value) 232 | { 233 | Master master = GetMaster(); 234 | master.Volume = value; 235 | SetMaster(master); 236 | } 237 | /// 238 | /// 音量を取得します 239 | /// 240 | /// 音量 241 | public float GetVolume() 242 | { 243 | return GetMaster().Volume; 244 | } 245 | /// 246 | /// 話速を設定します 247 | /// 248 | /// 0.5~4.0 249 | public void SetSpeed(float value) 250 | { 251 | Master master = GetMaster(); 252 | master.Speed = value; 253 | SetMaster(master); 254 | } 255 | /// 256 | /// 話速を取得します 257 | /// 258 | /// 話速 259 | public float GetSpeed() 260 | { 261 | return GetMaster().Speed; 262 | } 263 | 264 | /// 265 | /// 高さを設定します 266 | /// 267 | /// 0.5~2.0 268 | public void SetPitch(float value) 269 | { 270 | Master master = GetMaster(); 271 | master.Pitch = value; 272 | SetMaster(master); 273 | } 274 | /// 275 | /// 高さを取得します 276 | /// 277 | /// 高さ 278 | public float GetPitch() 279 | { 280 | return GetMaster().Pitch; 281 | } 282 | /// 283 | /// 抑揚を設定します 284 | /// 285 | /// 0.0~2.0 286 | public void SetPitchRange(float value) 287 | { 288 | Master master = GetMaster(); 289 | master.PitchRange = value; 290 | SetMaster(master); 291 | } 292 | /// 293 | /// 抑揚を取得します 294 | /// 295 | /// 抑揚 296 | public float GetPitchRange() 297 | { 298 | return GetMaster().PitchRange; 299 | } 300 | 301 | #region IDisposable Support 302 | private bool disposedValue = false; 303 | 304 | protected virtual void Dispose(bool disposing) 305 | { 306 | if (!disposedValue) 307 | { 308 | if (disposing) 309 | { 310 | if (_ttsControl != null) 311 | { 312 | _ttsControl.Disconnect(); 313 | } 314 | } 315 | disposedValue = true; 316 | } 317 | } 318 | 319 | public void Dispose() 320 | { 321 | Dispose(true); 322 | } 323 | #endregion 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/Speech/Controller/AIVOICEEnumerator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Xml.Linq; 9 | 10 | namespace Speech 11 | { 12 | class AIVOICEEnumerator : Voiceroid2Enumerator 13 | { 14 | public AIVOICEEnumerator() 15 | { 16 | // A.I.VOICEの一覧は下記で取得できる 17 | // 下記ファイルはAIVOICE Editor終了時に生成されるため、一度 起動・終了 18 | // しておくこと 19 | string path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) 20 | + @"\AI\A.I.VOICE Editor\1.0\Standard.settings"; 21 | Initialize(path, "AIVOICE"); 22 | } 23 | 24 | internal override string GetInstalledPath() 25 | { 26 | string installPath = @"SOFTWARE\AI\AIVoice\AIVoiceEditor\1.0"; 27 | 28 | string result = ""; 29 | RegistryKey key64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64); 30 | RegistryKey install = key64.OpenSubKey(installPath); 31 | if (install != null) 32 | { 33 | var key = install.GetValue("InstallDir"); 34 | if (key != null) 35 | { 36 | var location = key.ToString(); 37 | result = Path.Combine(location, @"AIVoiceEditor.exe"); 38 | } 39 | } 40 | return result; 41 | } 42 | public override SpeechEngineInfo[] GetSpeechEngineInfo() 43 | { 44 | List info = new List(); 45 | string path = GetInstalledPath(); 46 | // インストール先のパスが見つからない場合はSpeechEngineInfoを追加しない 47 | if (!string.IsNullOrEmpty(path)) 48 | { 49 | foreach (var v in _name) 50 | { 51 | info.Add(new SpeechEngineInfo { EngineName = EngineName, EnginePath = path, LibraryName = v, Is64BitProcess = true }) ; 52 | } 53 | } 54 | return info.ToArray(); 55 | } 56 | public override ISpeechController GetControllerInstance(SpeechEngineInfo info) 57 | { 58 | return EngineName == info.EngineName ? new AIVOICEController(info) : null; 59 | } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Speech/Controller/COEIROINKController.cs: -------------------------------------------------------------------------------- 1 | using Codeer.Friendly; 2 | using Codeer.Friendly.Windows; 3 | using Codeer.Friendly.Windows.Grasp; 4 | using RM.Friendly.WPFStandardControls; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel; 8 | using System.Diagnostics; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Net; 12 | using System.Net.Http; 13 | using System.Reflection; 14 | using System.Runtime.InteropServices; 15 | using System.Text; 16 | using System.Threading; 17 | using System.Threading.Tasks; 18 | using System.Windows.Forms; 19 | using System.Windows.Media.Animation; 20 | using System.Windows.Threading; 21 | 22 | namespace Speech 23 | { 24 | /// 25 | /// COEIROINK 操作クラス 26 | /// 27 | public class COEIROINKController : VOICEVOXController 28 | { 29 | public COEIROINKController(SpeechEngineInfo info) :base(info) 30 | { 31 | Info = info; 32 | _enumerator = new COEIROINKEnumerator(); 33 | _baseUrl = _enumerator.BaseUrl; 34 | _libraryName = info.LibraryName; 35 | } 36 | 37 | } 38 | } -------------------------------------------------------------------------------- /src/Speech/Controller/COEIROINKEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Http; 7 | using System.Runtime.Serialization.Json; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Xml.Linq; 11 | 12 | namespace Speech 13 | { 14 | public class COEIROINKEnumerator : VOICEVOXEnumerator 15 | { 16 | public COEIROINKEnumerator() 17 | { 18 | Initialize("COEIROINK", "http://127.0.0.1:50031"); 19 | } 20 | public override ISpeechController GetControllerInstance(SpeechEngineInfo info) 21 | { 22 | return EngineName == info.EngineName ? new COEIROINKController(info) : null; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Speech/Controller/CeVIO64Controller.cs: -------------------------------------------------------------------------------- 1 | using Codeer.Friendly; 2 | using Codeer.Friendly.Windows; 3 | using Codeer.Friendly.Windows.Grasp; 4 | using RM.Friendly.WPFStandardControls; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel; 8 | using System.Diagnostics; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Reflection; 12 | using System.Runtime.InteropServices; 13 | using System.Text; 14 | using System.Threading; 15 | using System.Threading.Tasks; 16 | using System.Windows.Forms; 17 | using System.Windows.Media.Animation; 18 | using System.Windows.Threading; 19 | 20 | namespace Speech 21 | { 22 | /// 23 | /// CeVIO 操作クラス 24 | /// 25 | public class CeVIO64Controller : IDisposable, ISpeechController 26 | { 27 | public SpeechEngineInfo Info { get; private set; } 28 | 29 | string _libraryName; 30 | dynamic _talker = null; 31 | Assembly _assembly; 32 | Type _serviceControl; 33 | 34 | CeVIO64Enumerator _cevio; 35 | 36 | public CeVIO64Controller(SpeechEngineInfo info) 37 | { 38 | Info = info; 39 | 40 | _cevio = new CeVIO64Enumerator(); 41 | _libraryName = info.LibraryName; 42 | } 43 | 44 | /// 45 | /// 音声再生が完了したときに発生するイベント 46 | /// 47 | public event EventHandler Finished; 48 | protected virtual void OnFinished() 49 | { 50 | EventArgs se = new EventArgs(); 51 | Finished?.Invoke(this, se); 52 | } 53 | 54 | /// 55 | /// CeVIO が起動中かどうかを確認 56 | /// 57 | /// 起動中であれば true 58 | public bool IsActive() 59 | { 60 | string name = Path.GetFileNameWithoutExtension(Info.EnginePath); 61 | Process[] localByName = Process.GetProcessesByName(name); 62 | 63 | if (localByName.Length > 0) 64 | { 65 | return true; 66 | } 67 | return false; 68 | } 69 | 70 | /// 71 | /// CeVIO を起動する。すでに起動している場合には起動しているものを操作対象とする。 72 | /// 73 | public void Activate() 74 | { 75 | _assembly = Assembly.LoadFrom(_cevio.AssemblyPath); 76 | _serviceControl = _assembly.GetType("CeVIO.Talk.RemoteService.ServiceControl"); 77 | 78 | //// 【CeVIO Creative Studio】起動 79 | //ServiceControl.StartHost(false); 80 | MethodInfo startHost = _serviceControl.GetMethod("StartHost"); 81 | startHost.Invoke(null, new object[] { false }); 82 | 83 | _talker = Activator.CreateInstance(_assembly.GetType("CeVIO.Talk.RemoteService.Talker"), new object[] { Info.LibraryName }); 84 | } 85 | 86 | /// 87 | /// 指定した文字列を再生します 88 | /// 89 | /// 再生する文字列 90 | public void Play(string text) 91 | { 92 | var state = _talker.Speak(text); 93 | state.Wait(); 94 | OnFinished(); 95 | } 96 | 97 | /// 98 | /// このメソッドは無効です。発話する文字列を指定してください。 99 | /// 100 | public void Play() 101 | { 102 | } 103 | /// 104 | /// 再生を停止します 105 | /// 106 | public void Stop() 107 | { 108 | } 109 | 110 | enum EffectType { Volume = 0, Speed = 1, Pitch = 2, PitchRange = 3} 111 | /// 112 | /// 音量を設定します 113 | /// 114 | /// 0.0~2.0 115 | public void SetVolume(float value) 116 | { 117 | if (value > 2) 118 | { 119 | value = 2; 120 | } 121 | else if (value < 0) 122 | { 123 | value = 0; 124 | } 125 | _talker.Volume = (uint)(value * 50); 126 | } 127 | /// 128 | /// 音量を取得します 129 | /// 130 | /// 音量 131 | public float GetVolume() 132 | { 133 | return _talker.Volume / 50f; 134 | } 135 | /// 136 | /// 話速を設定します 137 | /// 138 | /// 0.5~4.0 139 | public void SetSpeed(float value) 140 | { 141 | if (value > 2) 142 | { 143 | value = 2; 144 | } 145 | else if (value < 0) 146 | { 147 | value = 0; 148 | } 149 | _talker.Speed = (uint)(value * 50); 150 | } 151 | /// 152 | /// 話速を取得します 153 | /// 154 | /// 話速 155 | public float GetSpeed() 156 | { 157 | return _talker.Speed / 50f; 158 | } 159 | 160 | /// 161 | /// 高さを設定します 162 | /// 163 | /// 0.5~2.0 164 | public void SetPitch(float value) 165 | { 166 | if (value > 2) 167 | { 168 | value = 2; 169 | } 170 | else if (value < 0) 171 | { 172 | value = 0; 173 | } 174 | _talker.Tone = (uint)(value * 50); 175 | } 176 | /// 177 | /// 高さを取得します 178 | /// 179 | /// 高さ 180 | public float GetPitch() 181 | { 182 | return _talker.Tone / 50f; 183 | } 184 | /// 185 | /// 抑揚を設定します 186 | /// 187 | /// 0.0~2.0 188 | public void SetPitchRange(float value) 189 | { 190 | if (value > 2) 191 | { 192 | value = 2; 193 | } 194 | else if (value < 0) 195 | { 196 | value = 0; 197 | } 198 | _talker.ToneScale = (uint)(value * 50); 199 | } 200 | /// 201 | /// 抑揚を取得します 202 | /// 203 | /// 抑揚 204 | public float GetPitchRange() 205 | { 206 | return _talker.ToneScale / 50f; 207 | } 208 | 209 | /// 210 | /// 声色を設定します 211 | /// 212 | /// パラメータ名 213 | /// 0~100 214 | public void SetVoiceParam(string Name, uint value) 215 | { 216 | if (value > 100) 217 | { 218 | value = 100; 219 | } 220 | else if (value < 0) 221 | { 222 | value = 0; 223 | } 224 | _talker.Components.ByName(Name).Value = (uint)(value); 225 | } 226 | /// 227 | /// 声色を取得します 228 | /// 229 | /// パラメータ名 230 | /// パラメータ値 231 | public uint GetVoiceParam(string Name) 232 | { 233 | return _talker.Components.ByName(Name).Value; 234 | } 235 | /// 236 | /// 声質を設定します 237 | /// 238 | /// 0.0~100.0 239 | public void SetVoiceQuality(uint value) 240 | { 241 | if (value > 100) 242 | { 243 | value = 100; 244 | } 245 | else if (value < 0) 246 | { 247 | value = 0; 248 | } 249 | _talker.Alpha = (uint)(value); 250 | } 251 | /// 252 | /// 声質を取得します 253 | /// 254 | /// パラメータ名 255 | /// パラメータ値 256 | public uint GetVoiceQuality() 257 | { 258 | return _talker.Alpha; 259 | } 260 | 261 | #region IDisposable Support 262 | private bool disposedValue = false; 263 | 264 | protected virtual void Dispose(bool disposing) 265 | { 266 | if (!disposedValue) 267 | { 268 | if (disposing) 269 | { 270 | // CeVIO を終了する場合はコメントを外す 271 | //MethodInfo closeHost = _serviceControl.GetMethod("CloseHost"); 272 | //var hostCloseMode = _assembly.GetType("CeVIO.Talk.RemoteService.HostCloseMode"); 273 | //var mode = Enum.Parse(hostCloseMode, "Interrupt"); // Default, Interrupt, NotCancelable 274 | //closeHost.Invoke(null, new object[] { mode }); 275 | } 276 | disposedValue = true; 277 | } 278 | } 279 | 280 | public void Dispose() 281 | { 282 | Dispose(true); 283 | } 284 | #endregion 285 | } 286 | } -------------------------------------------------------------------------------- /src/Speech/Controller/CeVIO64Enumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Xml.Linq; 8 | 9 | namespace Speech 10 | { 11 | public class CeVIO64Enumerator : ISpeechEnumerator 12 | { 13 | string[] _name = new string[0]; 14 | string _installedPath = ""; 15 | public const string EngineName = "CeVIO64"; 16 | public CeVIO64Enumerator() 17 | { 18 | Initialize(); 19 | } 20 | 21 | public string AssemblyPath { get; private set; } 22 | private void Initialize() 23 | { 24 | List presetName = new List(); 25 | 26 | // CeVIO CS7 を探す 27 | string cevioPath = Environment.ExpandEnvironmentVariables("%ProgramW6432%") 28 | + @"\CeVIO\CeVIO Creative Studio (64bit)"; 29 | string cevio32Path = Environment.ExpandEnvironmentVariables("%ProgramW6432%") 30 | + @"\CeVIO\CeVIO Creative Studio"; 31 | _installedPath = cevioPath + @"\CeVIO Creative Studio.exe"; 32 | 33 | if (Directory.Exists(cevioPath) && File.Exists(_installedPath)) 34 | { 35 | AssemblyPath = cevioPath + @"\CeVIO.Talk.RemoteService.dll"; 36 | // CeVIOを起動せずにインストールされた音源一覧を取得する 37 | string[] talkDirectory = Directory.GetDirectories(Path.Combine(cevioPath, @"Configuration\VocalSource\Talk")); 38 | foreach (var d in talkDirectory) 39 | { 40 | string config = Path.Combine(d, "setting.cfg"); 41 | if (File.Exists(config)) 42 | { 43 | var xml = XDocument.Load(config); 44 | var doc = xml.Element("VocalSource"); 45 | string name = doc.Attribute("Name").Value; 46 | presetName.Add(name); 47 | } 48 | } 49 | // IA/ONEはフォルダが異なる 50 | // https://github.com/ksasao/TTSController/issues/11 51 | string[] talkDirectoryIAONE = Directory.GetDirectories(Path.Combine(cevio32Path, @"Configuration\VocalSource\Talk")); 52 | foreach (var d in talkDirectoryIAONE) 53 | { 54 | string config = Path.Combine(d, "setting.cfg"); 55 | if (File.Exists(config)) 56 | { 57 | var xml = XDocument.Load(config); 58 | var doc = xml.Element("VocalSource"); 59 | string name = doc.Attribute("Name").Value; 60 | presetName.Add(name); 61 | } 62 | } 63 | } 64 | 65 | _name = presetName.ToArray(); 66 | } 67 | public SpeechEngineInfo[] GetSpeechEngineInfo() 68 | { 69 | List info = new List(); 70 | foreach (var v in _name) 71 | { 72 | info.Add(new SpeechEngineInfo { 73 | EngineName = EngineName, 74 | EnginePath = _installedPath, 75 | LibraryName = v, 76 | Is64BitProcess = true 77 | }); 78 | } 79 | return info.ToArray(); 80 | } 81 | 82 | public ISpeechController GetControllerInstance(SpeechEngineInfo info) 83 | { 84 | return EngineName == info.EngineName ? new CeVIO64Controller(info) : null; 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/Speech/Controller/CeVIOAIController.cs: -------------------------------------------------------------------------------- 1 | using Codeer.Friendly; 2 | using Codeer.Friendly.Windows; 3 | using Codeer.Friendly.Windows.Grasp; 4 | using RM.Friendly.WPFStandardControls; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel; 8 | using System.Diagnostics; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Reflection; 12 | using System.Runtime.InteropServices; 13 | using System.Text; 14 | using System.Threading; 15 | using System.Threading.Tasks; 16 | using System.Windows.Forms; 17 | using System.Windows.Media.Animation; 18 | using System.Windows.Threading; 19 | 20 | namespace Speech 21 | { 22 | /// 23 | /// CeVIO 操作クラス 24 | /// 25 | public class CeVIOAIController : IDisposable, ISpeechController 26 | { 27 | public SpeechEngineInfo Info { get; private set; } 28 | 29 | string _libraryName; 30 | dynamic _talker = null; 31 | Assembly _assembly; 32 | Type _serviceControl; 33 | 34 | CeVIOAIEnumerator _cevio; 35 | 36 | public CeVIOAIController(SpeechEngineInfo info) 37 | { 38 | Info = info; 39 | 40 | _cevio = new CeVIOAIEnumerator(); 41 | _libraryName = info.LibraryName; 42 | } 43 | 44 | /// 45 | /// 音声再生が完了したときに発生するイベント 46 | /// 47 | public event EventHandler Finished; 48 | protected virtual void OnFinished() 49 | { 50 | EventArgs se = new EventArgs(); 51 | Finished?.Invoke(this, se); 52 | } 53 | 54 | /// 55 | /// CeVIO が起動中かどうかを確認 56 | /// 57 | /// 起動中であれば true 58 | public bool IsActive() 59 | { 60 | string name = Path.GetFileNameWithoutExtension(Info.EnginePath); 61 | Process[] localByName = Process.GetProcessesByName(name); 62 | 63 | if (localByName.Length > 0) 64 | { 65 | return true; 66 | } 67 | return false; 68 | } 69 | 70 | /// 71 | /// CeVIO を起動する。すでに起動している場合には起動しているものを操作対象とする。 72 | /// 73 | public void Activate() 74 | { 75 | _assembly = Assembly.LoadFrom(_cevio.AssemblyPath); 76 | _serviceControl = _assembly.GetType("CeVIO.Talk.RemoteService2.ServiceControl2"); 77 | 78 | //// 【CeVIO AI】起動 79 | //ServiceControl.StartHost(false); 80 | MethodInfo startHost = _serviceControl.GetMethod("StartHost"); 81 | startHost.Invoke(null, new object[] { false }); 82 | 83 | _talker = Activator.CreateInstance(_assembly.GetType("CeVIO.Talk.RemoteService2.Talker2"), new object[] { Info.LibraryName }); 84 | } 85 | 86 | /// 87 | /// 指定した文字列を再生します 88 | /// 89 | /// 再生する文字列 90 | public void Play(string text) 91 | { 92 | var state = _talker.Speak(text); 93 | state.Wait(); 94 | OnFinished(); 95 | } 96 | 97 | /// 98 | /// このメソッドは無効です。発話する文字列を指定してください。 99 | /// 100 | public void Play() 101 | { 102 | } 103 | /// 104 | /// 再生を停止します 105 | /// 106 | public void Stop() 107 | { 108 | } 109 | 110 | enum EffectType { Volume = 0, Speed = 1, Pitch = 2, PitchRange = 3} 111 | /// 112 | /// 音量を設定します 113 | /// 114 | /// 0.0~2.0 115 | public void SetVolume(float value) 116 | { 117 | if (value > 2) 118 | { 119 | value = 2; 120 | } 121 | else if (value < 0) 122 | { 123 | value = 0; 124 | } 125 | _talker.Volume = (uint)(value * 50); 126 | } 127 | /// 128 | /// 音量を取得します 129 | /// 130 | /// 音量 131 | public float GetVolume() 132 | { 133 | return _talker.Volume / 50f; 134 | } 135 | /// 136 | /// 話速を設定します 137 | /// 138 | /// 0.5~4.0 139 | public void SetSpeed(float value) 140 | { 141 | if (value > 2) 142 | { 143 | value = 2; 144 | } 145 | else if (value < 0) 146 | { 147 | value = 0; 148 | } 149 | _talker.Speed = (uint)(value * 50); 150 | } 151 | /// 152 | /// 話速を取得します 153 | /// 154 | /// 話速 155 | public float GetSpeed() 156 | { 157 | return _talker.Speed / 50f; 158 | } 159 | 160 | /// 161 | /// 高さを設定します 162 | /// 163 | /// 0.5~2.0 164 | public void SetPitch(float value) 165 | { 166 | if (value > 2) 167 | { 168 | value = 2; 169 | } 170 | else if (value < 0) 171 | { 172 | value = 0; 173 | } 174 | _talker.Tone = (uint)(value * 50); 175 | } 176 | /// 177 | /// 高さを取得します 178 | /// 179 | /// 高さ 180 | public float GetPitch() 181 | { 182 | return _talker.Tone / 50f; 183 | } 184 | /// 185 | /// 抑揚を設定します 186 | /// 187 | /// 0.0~2.0 188 | public void SetPitchRange(float value) 189 | { 190 | if (value > 2) 191 | { 192 | value = 2; 193 | } 194 | else if (value < 0) 195 | { 196 | value = 0; 197 | } 198 | _talker.ToneScale = (uint)(value * 50); 199 | } 200 | /// 201 | /// 抑揚を取得します 202 | /// 203 | /// 抑揚 204 | public float GetPitchRange() 205 | { 206 | return _talker.ToneScale / 50f; 207 | } 208 | 209 | /// 210 | /// 声色を設定します 211 | /// 212 | /// パラメータ名 213 | /// 0~100 214 | public void SetVoiceParam(string Name, uint value) 215 | { 216 | if (value > 100) 217 | { 218 | value = 100; 219 | } 220 | else if (value < 0) 221 | { 222 | value = 0; 223 | } 224 | _talker.Components.ByName(Name).Value = (uint)(value); 225 | } 226 | /// 227 | /// 声色を取得します 228 | /// 229 | /// パラメータ名 230 | /// パラメータ値 231 | public uint GetVoiceParam(string Name) 232 | { 233 | return _talker.Components.ByName(Name).Value; 234 | } 235 | 236 | /// 237 | /// 声質を設定します 238 | /// 239 | /// 0.0~100.0 240 | public void SetVoiceQuality(uint value) 241 | { 242 | if (value > 100) 243 | { 244 | value = 100; 245 | } 246 | else if (value < 0) 247 | { 248 | value = 0; 249 | } 250 | _talker.Alpha = (uint)(value); 251 | } 252 | /// 253 | /// 声質を取得します 254 | /// 255 | /// パラメータ名 256 | /// パラメータ値 257 | public uint GetVoiceQuality() 258 | { 259 | return _talker.Alpha; 260 | } 261 | 262 | 263 | #region IDisposable Support 264 | private bool disposedValue = false; 265 | 266 | protected virtual void Dispose(bool disposing) 267 | { 268 | if (!disposedValue) 269 | { 270 | if (disposing) 271 | { 272 | // CeVIO を終了する場合はコメントを外す 273 | //MethodInfo closeHost = _serviceControl.GetMethod("CloseHost"); 274 | //var hostCloseMode = _assembly.GetType("CeVIO.Talk.RemoteService2.HostCloseMode"); 275 | //var mode = Enum.Parse(hostCloseMode, "Interrupt"); // Default, Interrupt, NotCancelable 276 | //closeHost.Invoke(null, new object[] { mode }); 277 | } 278 | disposedValue = true; 279 | } 280 | } 281 | 282 | public void Dispose() 283 | { 284 | Dispose(true); 285 | } 286 | #endregion 287 | } 288 | } -------------------------------------------------------------------------------- /src/Speech/Controller/CeVIOAIEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Xml.Linq; 8 | 9 | namespace Speech 10 | { 11 | public class CeVIOAIEnumerator : ISpeechEnumerator 12 | { 13 | string[] _name = new string[0]; 14 | string _installedPath = ""; 15 | public const string EngineName = "CeVIOAI"; 16 | public CeVIOAIEnumerator() 17 | { 18 | Initialize(); 19 | } 20 | 21 | public string AssemblyPath { get; private set; } 22 | private void Initialize() 23 | { 24 | List presetName = new List(); 25 | 26 | // CeVIO AI を探す 27 | string cevioPath = Environment.ExpandEnvironmentVariables("%ProgramW6432%") 28 | + @"\CeVIO\CeVIO AI\"; 29 | _installedPath = cevioPath + @"\CeVIO AI.exe"; 30 | 31 | if (Directory.Exists(cevioPath) && File.Exists(_installedPath)) 32 | { 33 | AssemblyPath = cevioPath + @"\CeVIO.Talk.RemoteService2.dll"; 34 | // CeVIOを起動せずにインストールされた音源一覧を取得する 35 | string[] talkDirectory = Directory.GetDirectories(Path.Combine(cevioPath, @"Configuration\VocalSource\Talk")); 36 | foreach (var d in talkDirectory) 37 | { 38 | string config = Path.Combine(d, "setting.cfg"); 39 | if (File.Exists(config)) 40 | { 41 | var xml = XDocument.Load(config); 42 | var doc = xml.Element("VocalSource"); 43 | string name = doc.Attribute("Name").Value; 44 | // see https://github.com/ksasao/TTSController/issues/5 45 | if (name.IndexOf("Tsurumaki Maki (EN)") >= 0) 46 | { 47 | name = "弦巻マキ (英)"; 48 | } 49 | presetName.Add(name); 50 | } 51 | } 52 | } 53 | 54 | _name = presetName.ToArray(); 55 | } 56 | public SpeechEngineInfo[] GetSpeechEngineInfo() 57 | { 58 | List info = new List(); 59 | foreach (var v in _name) 60 | { 61 | info.Add(new SpeechEngineInfo { EngineName = EngineName, EnginePath = _installedPath, LibraryName = v, Is64BitProcess = true }); 62 | } 63 | return info.ToArray(); 64 | } 65 | 66 | public ISpeechController GetControllerInstance(SpeechEngineInfo info) 67 | { 68 | return EngineName == info.EngineName ? new CeVIOAIController(info) : null; 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/Speech/Controller/CeVIOController.cs: -------------------------------------------------------------------------------- 1 | using Codeer.Friendly; 2 | using Codeer.Friendly.Windows; 3 | using Codeer.Friendly.Windows.Grasp; 4 | using RM.Friendly.WPFStandardControls; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel; 8 | using System.Diagnostics; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Reflection; 12 | using System.Runtime.InteropServices; 13 | using System.Text; 14 | using System.Threading; 15 | using System.Threading.Tasks; 16 | using System.Windows.Forms; 17 | using System.Windows.Media.Animation; 18 | using System.Windows.Threading; 19 | 20 | namespace Speech 21 | { 22 | /// 23 | /// CeVIO 操作クラス 24 | /// 25 | public class CeVIOController : IDisposable, ISpeechController 26 | { 27 | public SpeechEngineInfo Info { get; private set; } 28 | 29 | string _libraryName; 30 | dynamic _talker = null; 31 | Assembly _assembly; 32 | Type _serviceControl; 33 | 34 | CeVIOEnumerator _cevio; 35 | 36 | public CeVIOController(SpeechEngineInfo info) 37 | { 38 | Info = info; 39 | 40 | _cevio = new CeVIOEnumerator(); 41 | _libraryName = info.LibraryName; 42 | } 43 | 44 | /// 45 | /// 音声再生が完了したときに発生するイベント 46 | /// 47 | public event EventHandler Finished; 48 | protected virtual void OnFinished() 49 | { 50 | EventArgs se = new EventArgs(); 51 | Finished?.Invoke(this, se); 52 | } 53 | 54 | /// 55 | /// CeVIO が起動中かどうかを確認 56 | /// 57 | /// 起動中であれば true 58 | public bool IsActive() 59 | { 60 | string name = Path.GetFileNameWithoutExtension(Info.EnginePath); 61 | Process[] localByName = Process.GetProcessesByName(name); 62 | 63 | if (localByName.Length > 0) 64 | { 65 | return true; 66 | } 67 | return false; 68 | } 69 | 70 | /// 71 | /// CeVIO を起動する。すでに起動している場合には起動しているものを操作対象とする。 72 | /// 73 | public void Activate() 74 | { 75 | _assembly = Assembly.LoadFrom(_cevio.AssemblyPath); 76 | _serviceControl = _assembly.GetType("CeVIO.Talk.RemoteService.ServiceControl"); 77 | 78 | //// 【CeVIO Creative Studio】起動 79 | //ServiceControl.StartHost(false); 80 | MethodInfo startHost = _serviceControl.GetMethod("StartHost"); 81 | startHost.Invoke(null, new object[] { false }); 82 | 83 | _talker = Activator.CreateInstance(_assembly.GetType("CeVIO.Talk.RemoteService.Talker"), new object[] { Info.LibraryName }); 84 | } 85 | 86 | /// 87 | /// 指定した文字列を再生します 88 | /// 89 | /// 再生する文字列 90 | public void Play(string text) 91 | { 92 | var state = _talker.Speak(text); 93 | state.Wait(); 94 | OnFinished(); 95 | } 96 | 97 | /// 98 | /// このメソッドは無効です。発話する文字列を指定してください。 99 | /// 100 | public void Play() 101 | { 102 | } 103 | /// 104 | /// 再生を停止します 105 | /// 106 | public void Stop() 107 | { 108 | } 109 | 110 | enum EffectType { Volume = 0, Speed = 1, Pitch = 2, PitchRange = 3} 111 | /// 112 | /// 音量を設定します 113 | /// 114 | /// 0.0~2.0 115 | public void SetVolume(float value) 116 | { 117 | if (value > 2) 118 | { 119 | value = 2; 120 | } 121 | else if (value < 0) 122 | { 123 | value = 0; 124 | } 125 | _talker.Volume = (uint)(value * 50); 126 | } 127 | /// 128 | /// 音量を取得します 129 | /// 130 | /// 音量 131 | public float GetVolume() 132 | { 133 | return _talker.Volume / 50f; 134 | } 135 | /// 136 | /// 話速を設定します 137 | /// 138 | /// 0.5~4.0 139 | public void SetSpeed(float value) 140 | { 141 | if (value > 2) 142 | { 143 | value = 2; 144 | } 145 | else if (value < 0) 146 | { 147 | value = 0; 148 | } 149 | _talker.Speed = (uint)(value * 50); 150 | } 151 | /// 152 | /// 話速を取得します 153 | /// 154 | /// 話速 155 | public float GetSpeed() 156 | { 157 | return _talker.Speed / 50f; 158 | } 159 | 160 | /// 161 | /// 高さを設定します 162 | /// 163 | /// 0.5~2.0 164 | public void SetPitch(float value) 165 | { 166 | if (value > 2) 167 | { 168 | value = 2; 169 | } 170 | else if (value < 0) 171 | { 172 | value = 0; 173 | } 174 | _talker.Tone = (uint)(value * 50); 175 | } 176 | /// 177 | /// 高さを取得します 178 | /// 179 | /// 高さ 180 | public float GetPitch() 181 | { 182 | return _talker.Tone / 50f; 183 | } 184 | /// 185 | /// 抑揚を設定します 186 | /// 187 | /// 0.0~2.0 188 | public void SetPitchRange(float value) 189 | { 190 | if (value > 2) 191 | { 192 | value = 2; 193 | } 194 | else if (value < 0) 195 | { 196 | value = 0; 197 | } 198 | _talker.ToneScale = (uint)(value * 50); 199 | } 200 | /// 201 | /// 抑揚を取得します 202 | /// 203 | /// 抑揚 204 | public float GetPitchRange() 205 | { 206 | return _talker.ToneScale / 50f; 207 | } 208 | 209 | /// 210 | /// 声色を設定します 211 | /// 212 | /// パラメータ名 213 | /// 0~100 214 | public void SetVoiceParam(string Name, uint value) 215 | { 216 | if (value > 100) 217 | { 218 | value = 100; 219 | } 220 | else if (value < 0) 221 | { 222 | value = 0; 223 | } 224 | _talker.Components.ByName(Name).Value = (uint)(value); 225 | } 226 | /// 227 | /// 声色を取得します 228 | /// 229 | /// パラメータ名 230 | /// パラメータ値 231 | public uint GetVoiceParam(string Name) 232 | { 233 | return _talker.Components.ByName(Name).Value; 234 | } 235 | /// 236 | /// 声質を設定します 237 | /// 238 | /// 0.0~100.0 239 | public void SetVoiceQuality(uint value) 240 | { 241 | if (value > 100) 242 | { 243 | value = 100; 244 | } 245 | else if (value < 0) 246 | { 247 | value = 0; 248 | } 249 | _talker.Alpha = (uint)(value); 250 | } 251 | /// 252 | /// 声質を取得します 253 | /// 254 | /// パラメータ名 255 | /// パラメータ値 256 | public uint GetVoiceQuality() 257 | { 258 | return _talker.Alpha; 259 | } 260 | 261 | #region IDisposable Support 262 | private bool disposedValue = false; 263 | 264 | protected virtual void Dispose(bool disposing) 265 | { 266 | if (!disposedValue) 267 | { 268 | if (disposing) 269 | { 270 | // CeVIO を終了する場合はコメントを外す 271 | //MethodInfo closeHost = _serviceControl.GetMethod("CloseHost"); 272 | //var hostCloseMode = _assembly.GetType("CeVIO.Talk.RemoteService.HostCloseMode"); 273 | //var mode = Enum.Parse(hostCloseMode, "Interrupt"); // Default, Interrupt, NotCancelable 274 | //closeHost.Invoke(null, new object[] { mode }); 275 | } 276 | disposedValue = true; 277 | } 278 | } 279 | 280 | public void Dispose() 281 | { 282 | Dispose(true); 283 | } 284 | #endregion 285 | } 286 | } -------------------------------------------------------------------------------- /src/Speech/Controller/CeVIOEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Xml.Linq; 8 | 9 | namespace Speech 10 | { 11 | public class CeVIOEnumerator : ISpeechEnumerator 12 | { 13 | string[] _name = new string[0]; 14 | string _installedPath = ""; 15 | public const string EngineName = "CeVIO"; 16 | public CeVIOEnumerator() 17 | { 18 | Initialize(); 19 | } 20 | 21 | public string AssemblyPath { get; private set; } 22 | private void Initialize() 23 | { 24 | List presetName = new List(); 25 | 26 | // CeVIO CS6 以前(32bit) を探す 27 | string cevioPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) 28 | + @"\CeVIO\CeVIO Creative Studio"; 29 | _installedPath = cevioPath + @"\CeVIO Creative Studio.exe"; 30 | 31 | if (Directory.Exists(cevioPath) && File.Exists(_installedPath)) 32 | { 33 | AssemblyPath = cevioPath + @"\CeVIO.Talk.RemoteService.dll"; 34 | // CeVIOを起動せずにインストールされた音源一覧を取得する 35 | string[] talkDirectory = Directory.GetDirectories(Path.Combine(cevioPath, @"Configuration\VocalSource\Talk")); 36 | foreach (var d in talkDirectory) 37 | { 38 | string config = Path.Combine(d, "setting.cfg"); 39 | if (File.Exists(config)) 40 | { 41 | var xml = XDocument.Load(config); 42 | var doc = xml.Element("VocalSource"); 43 | string name = doc.Attribute("Name").Value; 44 | presetName.Add(name); 45 | } 46 | } 47 | } 48 | 49 | _name = presetName.ToArray(); 50 | } 51 | public SpeechEngineInfo[] GetSpeechEngineInfo() 52 | { 53 | List info = new List(); 54 | foreach (var v in _name) 55 | { 56 | info.Add(new SpeechEngineInfo { 57 | EngineName = EngineName, 58 | EnginePath = _installedPath, 59 | LibraryName = v, 60 | Is64BitProcess = false}); 61 | } 62 | return info.ToArray(); 63 | } 64 | 65 | public ISpeechController GetControllerInstance(SpeechEngineInfo info) 66 | { 67 | return EngineName == info.EngineName ? new CeVIOController(info) : null; 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/Speech/Controller/GynoidTalkController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Speech 8 | { 9 | public class GynoidTalkController : Voiceroid2Controller 10 | { 11 | public GynoidTalkController(SpeechEngineInfo info) : base(info) 12 | { 13 | } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Speech/Controller/GynoidTalkEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Xml.Linq; 8 | 9 | namespace Speech 10 | { 11 | class GynoidTalkEnumerator : Voiceroid2Enumerator 12 | { 13 | public GynoidTalkEnumerator() 14 | { 15 | // ガイノイドトークの一覧は下記で取得できる 16 | // 下記ファイルはガイノイドトーク終了時に生成されるため、一度 ガイノイドトークを起動・終了 17 | // しておくこと 18 | string path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) 19 | + @"\Gynoid\GynoidTalk\1.0\Standard.settings"; 20 | Initialize(path, "GynoidTalk"); 21 | } 22 | 23 | internal override string GetInstalledPath() 24 | { 25 | string uninstall_path = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\"; 26 | // 32bit の場合 SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; 27 | 28 | string result = ""; 29 | Microsoft.Win32.RegistryKey uninstall = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(uninstall_path, false); 30 | if (uninstall != null) 31 | { 32 | foreach (string subKey in uninstall.GetSubKeyNames()) 33 | { 34 | Microsoft.Win32.RegistryKey appkey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(uninstall_path + "\\" + subKey, false); 35 | var key = appkey.GetValue("DisplayName"); 36 | if (key != null && key.ToString() == "ガイノイドTalk Editor") 37 | { 38 | var location = appkey.GetValue("InstallLocation").ToString(); 39 | result = Path.Combine(location, @"GynoidTalkEditor.exe"); 40 | break; 41 | } 42 | } 43 | } 44 | return result; 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Speech/Controller/ISpeechController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Speech 8 | { 9 | public interface ISpeechController 10 | { 11 | /// 12 | /// 音声合成エンジンの情報を取得します 13 | /// 14 | SpeechEngineInfo Info { get; } 15 | event EventHandler Finished; 16 | /// 17 | /// 音声合成エンジンを有効化します 18 | /// 19 | void Activate(); 20 | /// 21 | /// 音声合成エンジンに設定済みのテキストを再生します 22 | /// 23 | void Play(); 24 | /// 25 | /// 文字列を再生します 26 | /// 27 | /// 再生する文字列 28 | void Play(string text); 29 | /// 30 | /// 再生を停止します 31 | /// 32 | void Stop(); 33 | void Dispose(); 34 | /// 35 | /// 音量を取得します 36 | /// 37 | /// 取得した音量 38 | float GetVolume(); 39 | /// 40 | /// 音量を設定します 41 | /// 42 | /// 設定する音量 43 | void SetVolume(float value); 44 | /// 45 | /// 話速を取得します 46 | /// 47 | /// 取得した話速 48 | float GetSpeed(); 49 | /// 50 | /// 話速を設定します 51 | /// 52 | /// 設定する話速 53 | void SetSpeed(float value); 54 | /// 55 | /// 高さを取得します 56 | /// 57 | /// 取得した高さ 58 | float GetPitch(); 59 | /// 60 | /// 高さを設定します 61 | /// 62 | /// 設定する高さ 63 | void SetPitch(float value); 64 | /// 65 | /// 抑揚を取得します 66 | /// 67 | /// 設定する抑揚 68 | float GetPitchRange(); 69 | /// 70 | /// 抑揚を設定します 71 | /// 72 | /// 設定する抑揚 73 | void SetPitchRange(float value); 74 | /// 75 | /// アプリケーションが起動中かどうかを取得します 76 | /// 77 | /// 起動していれば true 78 | bool IsActive(); 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Speech/Controller/ISpeechEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Speech 8 | { 9 | interface ISpeechEnumerator 10 | { 11 | SpeechEngineInfo[] GetSpeechEngineInfo(); 12 | ISpeechController GetControllerInstance(SpeechEngineInfo info); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Speech/Controller/OtomachiUnaTalkController.cs: -------------------------------------------------------------------------------- 1 | using Codeer.Friendly; 2 | using Codeer.Friendly.Windows; 3 | using Codeer.Friendly.Windows.Grasp; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Diagnostics; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Runtime.InteropServices; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | using System.Windows.Forms; 14 | using System.Windows.Threading; 15 | 16 | namespace Speech 17 | { 18 | /// 19 | /// 音街ウナTalk Ex 操作クラス 20 | /// 21 | public class OtomachiUnaTalkController : IDisposable, ISpeechController 22 | { 23 | WindowsAppFriend _app; 24 | Process _process; 25 | WindowControl _root; 26 | System.Timers.Timer _timer; // 状態監視のためのタイマー 27 | bool _playStarting = false; 28 | 29 | [DllImport("User32.dll")] 30 | static extern int SetForegroundWindow(IntPtr hWnd); 31 | [DllImport("user32.dll", CharSet = CharSet.Auto)] 32 | private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); 33 | 34 | 35 | /// 36 | /// 音街ウナTalk Ex のフルパス 37 | /// 38 | public string OtomachiUnaPath { get; private set; } 39 | 40 | public SpeechEngineInfo Info { get; private set; } 41 | 42 | public OtomachiUnaTalkController(SpeechEngineInfo info) 43 | { 44 | Info = info; 45 | OtomachiUnaPath = info.EnginePath; 46 | _timer = new System.Timers.Timer(100); 47 | _timer.Elapsed += timer_Elapsed; 48 | } 49 | 50 | private void timer_Elapsed(object sender, EventArgs e) 51 | { 52 | WindowControl playButton = _root.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 0, 3); 53 | AppVar button = playButton.AppVar; 54 | string text = (string)button["Text"]().Core; 55 | if (!_playStarting && text.Trim() == "再生") 56 | { 57 | _timer.Stop(); 58 | OnFinished(); 59 | } 60 | _playStarting = false; 61 | } 62 | 63 | /// 64 | /// 音声再生が完了したときに発生するイベント 65 | /// 66 | public event EventHandler Finished; 67 | protected virtual void OnFinished() 68 | { 69 | EventArgs se = new EventArgs(); 70 | Finished?.Invoke(this, se); 71 | } 72 | 73 | /// 74 | /// 起動中かどうかを確認 75 | /// 76 | /// 起動中であれば true 77 | public bool IsActive() 78 | { 79 | string name = Path.GetFileNameWithoutExtension(OtomachiUnaPath); 80 | Process[] localByName = Process.GetProcessesByName(name); 81 | foreach(var p in localByName) 82 | { 83 | if(p.MainModule.FileName == OtomachiUnaPath) 84 | { 85 | _process = p; 86 | return true; 87 | } 88 | } 89 | return false; 90 | } 91 | 92 | /// 93 | /// 起動する。すでに起動している場合には起動しているものを操作対象とする。 94 | /// 95 | public void Activate() 96 | { 97 | if (IsActive()) 98 | { 99 | _app = new WindowsAppFriend(_process); 100 | } 101 | else 102 | { 103 | _process = Process.Start(OtomachiUnaPath); 104 | _app = new WindowsAppFriend(_process); 105 | } 106 | _root = WindowControl.GetTopLevelWindows(_app)[0]; 107 | } 108 | 109 | /// 110 | /// 指定した文字列を再生します 111 | /// 112 | /// 再生する文字列 113 | public void Play(string text) 114 | { 115 | WindowControl speechTextBox = _root.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 1); 116 | AppVar textbox = speechTextBox.AppVar; 117 | textbox["Text"](text); 118 | Play(); 119 | } 120 | /// 121 | /// 音街ウナTalk に入力された文字列を再生します 122 | /// 123 | public void Play() 124 | { 125 | WindowControl playButton = _root.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 0, 3); 126 | AppVar button = playButton.AppVar; 127 | string text = (string)button["Text"]().Core; 128 | if(text.Trim() == "再生") 129 | { 130 | button["PerformClick"](); 131 | _playStarting = true; 132 | _timer.Start(); 133 | } 134 | } 135 | /// 136 | /// 音街ウナTalk の再生を停止します(停止ボタンを押す) 137 | /// 138 | public void Stop() 139 | { 140 | WindowControl stopButton = _root.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 0, 2); 141 | AppVar button = stopButton.AppVar; 142 | button["PerformClick"](); 143 | } 144 | 145 | enum EffectType { Volume = 8, Speed = 9, Pitch = 10, PitchRange = 11} 146 | /// 147 | /// 音量を設定します 148 | /// 149 | /// 0.0~2.0 150 | public void SetVolume(float value) 151 | { 152 | SetEffect(EffectType.Volume, value); 153 | } 154 | /// 155 | /// 音量を取得します 156 | /// 157 | /// 音量 158 | public float GetVolume() 159 | { 160 | return GetEffect(EffectType.Volume); 161 | } 162 | /// 163 | /// 話速を設定します 164 | /// 165 | /// 0.5~4.0 166 | public void SetSpeed(float value) 167 | { 168 | SetEffect(EffectType.Speed,value); 169 | } 170 | /// 171 | /// 話速を取得します 172 | /// 173 | /// 話速 174 | public float GetSpeed() 175 | { 176 | return GetEffect(EffectType.Speed); 177 | } 178 | 179 | /// 180 | /// 高さを設定します 181 | /// 182 | /// 0.5~2.0 183 | public void SetPitch(float value) 184 | { 185 | SetEffect(EffectType.Pitch, value); 186 | ChangeToVoiceEffect(); 187 | } 188 | /// 189 | /// 高さを取得します 190 | /// 191 | /// 高さ 192 | public float GetPitch() 193 | { 194 | return GetEffect(EffectType.Pitch); 195 | } 196 | /// 197 | /// 抑揚を設定します 198 | /// 199 | /// 0.0~2.0 200 | public void SetPitchRange(float value) 201 | { 202 | SetEffect(EffectType.PitchRange, value); 203 | } 204 | /// 205 | /// 抑揚を取得します 206 | /// 207 | /// 抑揚 208 | public float GetPitchRange() 209 | { 210 | return GetEffect(EffectType.PitchRange); 211 | } 212 | 213 | private void SetEffect(EffectType t, float value) 214 | { 215 | ChangeToVoiceEffect(); 216 | int index = (int)t; 217 | WindowControl control = _root.IdentifyFromZIndex(2, 0, 0, 0, 0, 0, 0, index); 218 | AppVar v = control.AppVar; 219 | v["Focus"](); 220 | v["Text"](string.Format("{0:0.00}", value)); 221 | 222 | // TODO: 音街ウナTalkでは数値を変更するだけでは変更が行われないため何らかの方法が必要 223 | 224 | } 225 | private float GetEffect(EffectType t) 226 | { 227 | ChangeToVoiceEffect(); 228 | int index = (int)t; 229 | WindowControl control = _root.IdentifyFromZIndex(2, 0, 0, 0, 0, 0, 0, index); 230 | AppVar v = control.AppVar; 231 | return Convert.ToSingle((string)v["Text"]().Core); 232 | } 233 | 234 | 235 | /// 236 | /// 音声効果タブを選択します 237 | /// 238 | private void ChangeToVoiceEffect() 239 | { 240 | RestoreMinimizedWindow(); 241 | WindowControl tabControl = _root.IdentifyFromZIndex(2, 0, 0, 0, 0); 242 | AppVar tab = tabControl.AppVar; 243 | tab["SelectedIndex"](2); 244 | } 245 | private void RestoreMinimizedWindow() 246 | { 247 | const uint WM_SYSCOMMAND = 0x0112; 248 | const int SC_RESTORE = 0xF120; 249 | FormWindowState state = (FormWindowState)_root["WindowState"]().Core; 250 | if (state == FormWindowState.Minimized) 251 | { 252 | SendMessage(_root.Handle, WM_SYSCOMMAND, 253 | new IntPtr(SC_RESTORE), IntPtr.Zero); 254 | } 255 | } 256 | 257 | #region IDisposable Support 258 | private bool disposedValue = false; 259 | 260 | protected virtual void Dispose(bool disposing) 261 | { 262 | if (!disposedValue) 263 | { 264 | if (disposing) 265 | { 266 | if(_app != null) 267 | { 268 | _app.Dispose(); 269 | } 270 | } 271 | disposedValue = true; 272 | } 273 | } 274 | 275 | public void Dispose() 276 | { 277 | Dispose(true); 278 | } 279 | #endregion 280 | } 281 | } -------------------------------------------------------------------------------- /src/Speech/Controller/OtomachiUnaTalkEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Xml.Linq; 8 | 9 | namespace Speech 10 | { 11 | 12 | public class OtomachiUnaTalkEnumerator : ISpeechEnumerator 13 | { 14 | 15 | class Data 16 | { 17 | public string Name { get; internal set; } 18 | public string Path { get; internal set; } 19 | } 20 | Data[] _info; 21 | public const string EngineName = "OtomachiUna_Talk_Ex"; 22 | 23 | 24 | public OtomachiUnaTalkEnumerator() 25 | { 26 | Initialize(); 27 | } 28 | private void Initialize() 29 | { 30 | // 音街ウナTalkExのパス 31 | string path = System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) 32 | + @"\INTERNET Co.,Ltd\OtomachiUnaTalk Ex\"; 33 | List data = new List(); 34 | if (Directory.Exists(path)) 35 | { 36 | Data d = new Data(); 37 | d.Name = "音街ウナ"; 38 | d.Path = Path.Combine(path, "OtomachiUnaTalkEx.exe"); 39 | data.Add(d); 40 | } 41 | _info = data.ToArray(); 42 | } 43 | 44 | public SpeechEngineInfo[] GetSpeechEngineInfo() 45 | { 46 | List info = new List(); 47 | foreach (var v in _info) 48 | { 49 | info.Add(new SpeechEngineInfo { EngineName = EngineName, EnginePath = v.Path, LibraryName = v.Name }); 50 | } 51 | return info.ToArray(); 52 | } 53 | public ISpeechController GetControllerInstance(SpeechEngineInfo info) 54 | { 55 | return EngineName == info.EngineName ? new OtomachiUnaTalkController(info) : null; 56 | } 57 | 58 | 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Speech/Controller/SAPI5Controller.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | using System.Speech.Synthesis; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using System.Windows.Forms; 13 | 14 | namespace Speech 15 | { 16 | /// 17 | /// SAPI5 操作クラス 18 | /// 19 | public class SAPI5Controller : IDisposable, ISpeechController 20 | { 21 | SpeechSynthesizer synthesizer = null; 22 | string _voiceName; 23 | string _lastText=""; 24 | public SpeechEngineInfo Info { get; private set; } 25 | 26 | /// 27 | /// 音声再生が完了したときに発生するイベント 28 | /// 29 | public event EventHandler Finished; 30 | protected virtual void OnFinished() 31 | { 32 | EventArgs se = new EventArgs(); 33 | Finished?.Invoke(this, se); 34 | } 35 | 36 | public SAPI5Controller(SpeechEngineInfo info) 37 | { 38 | Info = info; 39 | _voiceName = info.LibraryName; 40 | } 41 | 42 | /// 43 | /// 音声合成が有効かどうかをチェックする 44 | /// 45 | /// 起動中であれば true 46 | public bool IsActive() 47 | { 48 | return synthesizer != null; 49 | } 50 | 51 | /// 52 | /// SAPI5を起動する 53 | /// 54 | public void Activate() 55 | { 56 | if (!IsActive()) 57 | { 58 | synthesizer = new SpeechSynthesizer(); 59 | var voice = synthesizer.GetInstalledVoices(); 60 | for (int i = 0; i < voice.Count; i++) 61 | { 62 | var v = voice[i].VoiceInfo.Name; 63 | if (v.IndexOf(_voiceName) >= 0) 64 | { 65 | synthesizer.SelectVoice(v); 66 | break; 67 | } 68 | } 69 | } 70 | } 71 | 72 | /// 73 | /// 指定した文字列を再生します 74 | /// 75 | /// 再生する文字列 76 | public async void Play(string text) 77 | { 78 | text = text ?? ""; 79 | text = text.Trim(); 80 | if (text == "") 81 | { 82 | OnFinished(); 83 | return; 84 | } 85 | await SpeechAsync(text); 86 | } 87 | 88 | private async Task SpeechAsync(string text) 89 | { 90 | await Task.Run(() => 91 | { 92 | _lastText = text; 93 | synthesizer.Speak(text); 94 | OnFinished(); 95 | }); 96 | 97 | } 98 | /// 99 | /// 最後に入力された文字列を再生します 100 | /// 101 | public void Play() 102 | { 103 | Play(_lastText); 104 | } 105 | /// 106 | /// 再生を停止します 107 | /// 108 | public void Stop() 109 | { 110 | // not implemented 111 | } 112 | 113 | /// 114 | /// 音量を設定します 115 | /// 116 | /// 0.0~2.0 117 | public void SetVolume(float value) 118 | { 119 | synthesizer.Volume = (int)(value * 100f); 120 | } 121 | /// 122 | /// 音量を取得します 123 | /// 124 | /// 音量(0.0~1.0) 125 | public float GetVolume() 126 | { 127 | return synthesizer.Volume / 100f; // SAPI5の音量は0-100で指定 128 | } 129 | /// 130 | /// 話速を設定します 131 | /// 132 | /// 0.5~4.0 133 | public void SetSpeed(float value) 134 | { 135 | synthesizer.Rate = (int)((value - 1f) * 10f); 136 | } 137 | /// 138 | /// 話速を取得します 139 | /// 140 | /// 話速 141 | public float GetSpeed() 142 | { 143 | return (synthesizer.Rate+10)/10f; //Rate: -10 ~ 10 (default:0) 144 | } 145 | 146 | /// 147 | /// 高さを設定します。SAPI5では無効です。 148 | /// 149 | /// 0.5~2.0 150 | public void SetPitch(float value) 151 | { 152 | // 何もしない 153 | } 154 | /// 155 | /// 高さを取得します。SAPI5では無効です。 156 | /// 157 | /// 高さ 158 | public float GetPitch() 159 | { 160 | return 1f; 161 | } 162 | /// 163 | /// 抑揚を設定します。SAPI5では無効です。 164 | /// 165 | /// 0.0~2.0 166 | public void SetPitchRange(float value) 167 | { 168 | // 何もしない 169 | } 170 | /// 171 | /// 抑揚を取得します。SAPI5では無効です。 172 | /// 173 | /// 抑揚 174 | public float GetPitchRange() 175 | { 176 | return 1f; 177 | } 178 | 179 | #region IDisposable Support 180 | private bool disposedValue = false; 181 | 182 | protected virtual void Dispose(bool disposing) 183 | { 184 | if (!disposedValue) 185 | { 186 | if (disposing) 187 | { 188 | if(synthesizer != null) 189 | { 190 | while(synthesizer.State == SynthesizerState.Speaking) 191 | { 192 | Thread.Sleep(100); 193 | } 194 | synthesizer.Dispose(); 195 | } 196 | } 197 | disposedValue = true; 198 | } 199 | } 200 | 201 | public void Dispose() 202 | { 203 | Dispose(true); 204 | } 205 | #endregion 206 | } 207 | } -------------------------------------------------------------------------------- /src/Speech/Controller/SAPI5Enumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Speech.Synthesis; 7 | 8 | namespace Speech 9 | { 10 | public class SAPI5Enumerator : ISpeechEnumerator 11 | { 12 | class Data 13 | { 14 | public string Name { get; internal set; } 15 | public string Path { get; internal set; } 16 | } 17 | public const string EngineName = "SAPI5"; 18 | 19 | Data[] _info; 20 | 21 | SpeechSynthesizer synthesizer = null; 22 | public SAPI5Enumerator() 23 | { 24 | Initialize(); 25 | } 26 | 27 | private void Initialize() 28 | { 29 | List sapi5 = new List(); 30 | 31 | synthesizer = new SpeechSynthesizer(); 32 | var voice = synthesizer.GetInstalledVoices(); 33 | for (int i = 0; i < voice.Count; i++) 34 | { 35 | var v = voice[i].VoiceInfo.Name; 36 | if (v.StartsWith("CeVIO")) 37 | { 38 | // CeVIOは 64bit Windows での SAPI経由での動作保証をしていないためスキップ 39 | // http://guide2.project-cevio.com/interface 40 | continue; 41 | } 42 | sapi5.Add(new Data { Name = v, Path = "" }); 43 | } 44 | _info = sapi5.ToArray(); 45 | } 46 | public SpeechEngineInfo[] GetSpeechEngineInfo() 47 | { 48 | List info = new List(); 49 | foreach (var v in _info) 50 | { 51 | info.Add(new SpeechEngineInfo { EngineName = EngineName, EnginePath = v.Path, LibraryName = v.Name }); 52 | } 53 | return info.ToArray(); 54 | } 55 | public ISpeechController GetControllerInstance(SpeechEngineInfo info) 56 | { 57 | return EngineName == info.EngineName ? new SAPI5Controller(info) : null; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Speech/Controller/SHAREVOXController.cs: -------------------------------------------------------------------------------- 1 | using Codeer.Friendly; 2 | using Codeer.Friendly.Windows; 3 | using Codeer.Friendly.Windows.Grasp; 4 | using RM.Friendly.WPFStandardControls; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel; 8 | using System.Diagnostics; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Net; 12 | using System.Net.Http; 13 | using System.Reflection; 14 | using System.Runtime.InteropServices; 15 | using System.Text; 16 | using System.Threading; 17 | using System.Threading.Tasks; 18 | using System.Windows.Forms; 19 | using System.Windows.Media.Animation; 20 | using System.Windows.Threading; 21 | 22 | namespace Speech 23 | { 24 | /// 25 | /// COEIROINK 操作クラス 26 | /// 27 | public class SHAREVOXController : VOICEVOXController 28 | { 29 | public SHAREVOXController(SpeechEngineInfo info) :base(info) 30 | { 31 | Info = info; 32 | _enumerator = new SHAREVOXEnumerator(); 33 | _baseUrl = _enumerator.BaseUrl; 34 | _libraryName = info.LibraryName; 35 | } 36 | 37 | } 38 | } -------------------------------------------------------------------------------- /src/Speech/Controller/SHAREVOXEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Http; 7 | using System.Runtime.Serialization.Json; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Xml.Linq; 11 | 12 | namespace Speech 13 | { 14 | public class SHAREVOXEnumerator : VOICEVOXEnumerator 15 | { 16 | public SHAREVOXEnumerator() 17 | { 18 | // https://github.com/SHAREVOX/sharevox_engine 19 | Initialize("SHAREVOX", "http://127.0.0.1:50025"); 20 | } 21 | public override ISpeechController GetControllerInstance(SpeechEngineInfo info) 22 | { 23 | return EngineName == info.EngineName ? new SHAREVOXController(info) : null; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Speech/Controller/VOICEPEAKController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | 13 | namespace Speech 14 | { 15 | public class VOICEPEAKController : IDisposable, ISpeechController 16 | { 17 | 18 | public SpeechEngineInfo Info { get; private set; } 19 | 20 | /// 21 | /// Voiceroid のフルパス 22 | /// 23 | public string VoiceroidPath { get; private set; } 24 | 25 | private string[] ExecuteVoicepeak(string args) 26 | { 27 | ProcessStartInfo psInfo = new ProcessStartInfo(); 28 | 29 | psInfo.FileName = Info.EnginePath; 30 | psInfo.CreateNoWindow = true; 31 | psInfo.UseShellExecute = false; 32 | psInfo.RedirectStandardOutput = true; 33 | psInfo.Arguments = args; 34 | 35 | using (Process p = Process.Start(psInfo)) 36 | { 37 | // Voicepeakは非同期実行されるのでプロセス終了後に標準出力を取り出す 38 | p.WaitForExit(10); 39 | 40 | // 行の整形 41 | string[] stdout = p.StandardOutput.ReadToEnd().Split('\n'); 42 | string[] output = stdout.Where(x => x.Trim().Length > 0).Select(x => x.Trim()).ToArray(); 43 | return output; 44 | } 45 | } 46 | 47 | public VOICEPEAKController(SpeechEngineInfo info) 48 | { 49 | Info = info; 50 | //emotions = ExecuteVoicepeak($"--list-emotion \"{info.LibraryName}\""); 51 | } 52 | 53 | 54 | /// 55 | /// 音声再生が完了したときに発生するイベント 56 | /// 57 | public event EventHandler Finished; 58 | protected virtual void OnFinished() 59 | { 60 | EventArgs se = new EventArgs(); 61 | Finished?.Invoke(this, se); 62 | } 63 | 64 | /// 65 | /// 起動中かどうかを確認 66 | /// 67 | /// 起動中であれば true 68 | public bool IsActive() 69 | { 70 | // VOICEPEAK は起動しているかどうかは問わない設計となっているので常にtrue 71 | return true; 72 | } 73 | 74 | /// 75 | /// 音声合成エンジンを起動する。すでに起動している場合には起動しているものを操作対象とする。 76 | /// 77 | public void Activate() 78 | { 79 | // 明示的に起動する必要はないので何もしない 80 | } 81 | 82 | /// 83 | /// 指定した文字列を再生します 84 | /// 85 | /// 再生する文字列 86 | public void Play(string text) 87 | { 88 | ExecuteVoicepeak($"-n \"{Info.LibraryName}\" -s \"{text}\""); 89 | using (SoundPlayer soundPlayer = new SoundPlayer()) 90 | { 91 | soundPlayer.Play("output.wav"); 92 | } 93 | } 94 | 95 | 96 | /// 97 | /// VOICEROID2 に入力された文字列を再生します 98 | /// 99 | public void Play() 100 | { 101 | 102 | } 103 | /// 104 | /// 再生を停止します 105 | /// 106 | public void Stop() 107 | { 108 | 109 | } 110 | 111 | enum EffectType { Volume = 0, Speed = 1, Pitch = 2, PitchRange = 3 } 112 | /// 113 | /// 音量を設定します 114 | /// 115 | /// 0.0~2.0 116 | public void SetVolume(float value) 117 | { 118 | SetEffect(EffectType.Volume, value); 119 | } 120 | /// 121 | /// 音量を取得します 122 | /// 123 | /// 音量 124 | public float GetVolume() 125 | { 126 | return GetEffect(EffectType.Volume); 127 | } 128 | /// 129 | /// 話速を設定します 130 | /// 131 | /// 0.5~4.0 132 | public void SetSpeed(float value) 133 | { 134 | SetEffect(EffectType.Speed, value); 135 | } 136 | /// 137 | /// 話速を取得します 138 | /// 139 | /// 話速 140 | public float GetSpeed() 141 | { 142 | return GetEffect(EffectType.Speed); 143 | } 144 | 145 | /// 146 | /// 高さを設定します 147 | /// 148 | /// 0.5~2.0 149 | public void SetPitch(float value) 150 | { 151 | SetEffect(EffectType.Pitch, value); 152 | } 153 | /// 154 | /// 高さを取得します 155 | /// 156 | /// 高さ 157 | public float GetPitch() 158 | { 159 | return GetEffect(EffectType.Pitch); 160 | } 161 | /// 162 | /// 抑揚を設定します 163 | /// 164 | /// 0.0~2.0 165 | public void SetPitchRange(float value) 166 | { 167 | SetEffect(EffectType.PitchRange, value); 168 | } 169 | /// 170 | /// 抑揚を取得します 171 | /// 172 | /// 抑揚 173 | public float GetPitchRange() 174 | { 175 | return GetEffect(EffectType.PitchRange); 176 | } 177 | 178 | private void SetEffect(EffectType t, float value) 179 | { 180 | } 181 | private float GetEffect(EffectType t) 182 | { 183 | return 0; 184 | } 185 | 186 | #region IDisposable Support 187 | private bool disposedValue = false; 188 | 189 | protected virtual void Dispose(bool disposing) 190 | { 191 | if (!disposedValue) 192 | { 193 | // Disposable ではあるが、実際にリソースを握っているわけではないのでそのまま返す 194 | disposedValue = true; 195 | } 196 | } 197 | 198 | public void Dispose() 199 | { 200 | Dispose(true); 201 | } 202 | #endregion 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/Speech/Controller/VOICEPEAKEnumerator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Speech 11 | { 12 | class VOICEPEAKEnumerator : ISpeechEnumerator 13 | { 14 | string path = ""; 15 | const string EngineName = "VOICEPEAK"; 16 | 17 | public VOICEPEAKEnumerator() 18 | { 19 | string[] files = GetInstalledPath(); 20 | if (files.Length > 0) 21 | { 22 | path = files[0]; 23 | } 24 | } 25 | 26 | private string[] ExecuteVoicepeak(string args) 27 | { 28 | ProcessStartInfo psInfo = new ProcessStartInfo(); 29 | 30 | psInfo.FileName = path; 31 | psInfo.CreateNoWindow = true; 32 | psInfo.UseShellExecute = false; 33 | psInfo.RedirectStandardOutput = true; 34 | psInfo.Arguments = args; 35 | 36 | using (Process p = Process.Start(psInfo)) 37 | { 38 | // Voicepeakは非同期実行されるのでプロセス終了後に標準出力を取り出す 39 | p.WaitForExit(10); 40 | 41 | // 行の整形 42 | string[] stdout = p.StandardOutput.ReadToEnd().Split('\n'); 43 | string[] output = stdout.Where(x => x.Trim().Length > 0).Select(x => x.Trim()).ToArray(); 44 | return output; 45 | } 46 | } 47 | 48 | private string[] GetInstalledPath() 49 | { 50 | List appPath = new List(); 51 | 52 | // インストーラでインストールされた64bitアプリを列挙 53 | string regKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"; 54 | using (RegistryKey localMachine64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) 55 | { 56 | RegistryKey subKey = localMachine64.OpenSubKey(regKey); 57 | 58 | // "voicepeak" を含むアプリケーションのみを抽出(6ナレーターとその他は別扱い) 59 | string[] names = subKey.GetSubKeyNames().Where(x => x.ToLower().IndexOf("voicepeak") >= 0).ToArray(); 60 | 61 | foreach (string name in names) 62 | { 63 | using (RegistryKey appkey = subKey.OpenSubKey(name)) 64 | { 65 | string path = appkey.GetValue("Inno Setup: App Path")?.ToString(); 66 | // バージョンが 1.2.1以上のものを抽出 67 | if (path != null) 68 | { 69 | string version = appkey.GetValue("DisplayVersion")?.ToString(); 70 | string[] vs = version.Split('.'); 71 | if (vs.Length >= 3) 72 | { 73 | try 74 | { 75 | int v = int.Parse(vs[0]) * 100 * 100 + int.Parse(vs[1]) * 100 + int.Parse(vs[2]); 76 | if (v >= 1 * 100 * 100 + 2 * 100 + 1) 77 | { 78 | appPath.Add(Path.Combine(path, "voicepeak.exe")); 79 | } 80 | } 81 | catch (Exception) 82 | { 83 | // バージョン判定できないので追加しない 84 | } 85 | } 86 | } 87 | 88 | } 89 | } 90 | 91 | } 92 | return appPath.ToArray(); 93 | } 94 | 95 | public SpeechEngineInfo[] GetSpeechEngineInfo() 96 | { 97 | List infoList = new List(); 98 | 99 | string[] narrators = ExecuteVoicepeak("--list-narrator"); 100 | foreach(var s in narrators) 101 | { 102 | SpeechEngineInfo info = new SpeechEngineInfo(); 103 | info.EngineName = EngineName; 104 | info.EnginePath = path; 105 | info.LibraryName = s; 106 | info.Is64BitProcess = true; 107 | infoList.Add(info); 108 | } 109 | return infoList.ToArray(); 110 | } 111 | 112 | public ISpeechController GetControllerInstance(SpeechEngineInfo info) 113 | { 114 | return EngineName == info.EngineName ? new VOICEPEAKController(info) : null; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Speech/Controller/VOICEVOXController.cs: -------------------------------------------------------------------------------- 1 | using Codeer.Friendly; 2 | using Codeer.Friendly.Windows; 3 | using Codeer.Friendly.Windows.Grasp; 4 | using RM.Friendly.WPFStandardControls; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel; 8 | using System.Diagnostics; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Net; 12 | using System.Net.Http; 13 | using System.Reflection; 14 | using System.Runtime.InteropServices; 15 | using System.Text; 16 | using System.Text.RegularExpressions; 17 | using System.Threading; 18 | using System.Threading.Tasks; 19 | using System.Windows.Forms; 20 | using System.Windows.Media.Animation; 21 | using System.Windows.Threading; 22 | 23 | namespace Speech 24 | { 25 | /// 26 | /// VOICEVOX 操作クラス 27 | /// 28 | public class VOICEVOXController : IDisposable, ISpeechController 29 | { 30 | public SpeechEngineInfo Info { get; internal set; } 31 | 32 | internal string _libraryName; 33 | internal string _baseUrl; 34 | 35 | internal VOICEVOXEnumerator _enumerator; 36 | internal float Volume { get; set; } = 1.0f; 37 | internal float Speed { get; set; } = 1.0f; 38 | internal float Pitch { get; set; } = 0.0f; 39 | internal float Intonation { get; set; } = 1.0f; 40 | 41 | public VOICEVOXController(SpeechEngineInfo info) 42 | { 43 | Info = info; 44 | _enumerator = new VOICEVOXEnumerator(); 45 | _baseUrl = _enumerator.BaseUrl; 46 | _libraryName = info.LibraryName; 47 | } 48 | 49 | /// 50 | /// 音声再生が完了したときに発生するイベント 51 | /// 52 | public event EventHandler Finished; 53 | protected virtual void OnFinished() 54 | { 55 | EventArgs se = new EventArgs(); 56 | Finished?.Invoke(this, se); 57 | } 58 | 59 | /// 60 | /// VOICEVOX が起動中かどうかを確認 61 | /// 62 | /// 起動中であれば true 63 | public bool IsActive() 64 | { 65 | using (var client = new HttpClient()) 66 | { 67 | var response = client.GetAsync($"{_baseUrl}/docs").GetAwaiter().GetResult(); 68 | return (response.StatusCode == HttpStatusCode.OK); 69 | } 70 | } 71 | 72 | /// 73 | /// VOICEVOX を起動する。すでに起動している場合には起動しているものを操作対象とする。 74 | /// 75 | public void Activate() 76 | { 77 | 78 | } 79 | 80 | private string UpdateParam(string str) 81 | { 82 | str = ReplaceParam(str, "volumeScale", Volume); 83 | str = ReplaceParam(str,"speedScale", Speed); 84 | str = ReplaceParam(str, "intonationScale", Intonation); 85 | str = ReplaceParam(str, "pitchScale", Pitch); 86 | return str; 87 | } 88 | 89 | private string ReplaceParam(string str, string key, float value) 90 | { 91 | // "pitchScale":0.0, 92 | string result = Regex.Replace(str, $"{key}\"\\s*?:\\s*?[\\d\\.]+", $"{key}\":{value:F2}"); 93 | return result; 94 | } 95 | 96 | /// 97 | /// 指定した文字列を再生します 98 | /// 99 | /// 再生する文字列 100 | public void Play(string text) 101 | { 102 | string tempFile = Path.GetTempFileName(); 103 | 104 | var content = new StringContent("", Encoding.UTF8, @"application/json"); 105 | var encodeText = Uri.EscapeDataString(text); 106 | 107 | int talkerNo = _enumerator.Names[_libraryName]; 108 | 109 | string queryData = ""; 110 | using (var client = new HttpClient()) 111 | { 112 | try 113 | { 114 | var response = client.PostAsync($"{_baseUrl}/audio_query?text={encodeText}&speaker={talkerNo}", content).GetAwaiter().GetResult(); 115 | if (response.StatusCode != HttpStatusCode.OK) { return; } 116 | queryData = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); 117 | 118 | // 音量等のパラメータを反映させる 119 | queryData = UpdateParam(queryData); 120 | 121 | content = new StringContent(queryData, Encoding.UTF8, @"application/json"); 122 | response = client.PostAsync($"{_baseUrl}/synthesis?speaker={talkerNo}", content).GetAwaiter().GetResult(); 123 | if (response.StatusCode != HttpStatusCode.OK) { return; } 124 | 125 | var soundData = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); 126 | 127 | using (var fileStream = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None)) 128 | { 129 | soundData.CopyTo(fileStream); 130 | 131 | } 132 | 133 | SoundPlayer sp = new SoundPlayer(); 134 | sp.Play(tempFile); 135 | } 136 | finally 137 | { 138 | OnFinished(); 139 | } 140 | } 141 | 142 | } 143 | 144 | /// 145 | /// このメソッドは無効です。発話する文字列を指定してください。 146 | /// 147 | public void Play() 148 | { 149 | } 150 | /// 151 | /// 再生を停止します 152 | /// 153 | public void Stop() 154 | { 155 | } 156 | 157 | /// 158 | /// 音量を設定します 159 | /// 160 | /// 0.0~2.0 161 | public void SetVolume(float value) 162 | { 163 | Volume = value; 164 | } 165 | /// 166 | /// 音量を取得します 167 | /// 168 | /// 音量 169 | public float GetVolume() 170 | { 171 | return Volume; 172 | } 173 | /// 174 | /// 話速を設定します 175 | /// 176 | /// 0.5~4.0 177 | public void SetSpeed(float value) 178 | { 179 | Speed = value; 180 | } 181 | /// 182 | /// 話速を取得します 183 | /// 184 | /// 話速 185 | public float GetSpeed() 186 | { 187 | return Speed; 188 | } 189 | 190 | /// 191 | /// 高さを設定します 192 | /// 193 | /// 0.5~2.0 194 | public void SetPitch(float value) 195 | { 196 | Pitch = value; 197 | } 198 | /// 199 | /// 高さを取得します 200 | /// 201 | /// 高さ 202 | public float GetPitch() 203 | { 204 | return Pitch; 205 | } 206 | /// 207 | /// 抑揚を設定します 208 | /// 209 | /// 0.0~2.0 210 | public void SetPitchRange(float value) 211 | { 212 | Intonation = value; 213 | } 214 | /// 215 | /// 抑揚を取得します:この関数は無効です 216 | /// 217 | /// 抑揚 218 | public float GetPitchRange() 219 | { 220 | return Intonation; 221 | } 222 | 223 | 224 | #region IDisposable Support 225 | private bool disposedValue = false; 226 | 227 | protected virtual void Dispose(bool disposing) 228 | { 229 | if (!disposedValue) 230 | { 231 | if (disposing) 232 | { 233 | 234 | } 235 | disposedValue = true; 236 | } 237 | } 238 | 239 | public void Dispose() 240 | { 241 | Dispose(true); 242 | } 243 | #endregion 244 | } 245 | } -------------------------------------------------------------------------------- /src/Speech/Controller/VOICEVOXEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Http; 7 | using System.Runtime.Serialization.Json; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Xml.Linq; 11 | 12 | namespace Speech 13 | { 14 | [System.Runtime.Serialization.DataContract] 15 | class Speaker 16 | { 17 | [System.Runtime.Serialization.DataMember] 18 | public string name { get; set; } 19 | [System.Runtime.Serialization.DataMember] 20 | public string speaker_uuid { get; set; } 21 | [System.Runtime.Serialization.DataMember] 22 | public Style[] styles { get; set; } 23 | [System.Runtime.Serialization.DataMember] 24 | public string version { get; set; } 25 | 26 | } 27 | [System.Runtime.Serialization.DataContract] 28 | class Style 29 | { 30 | [System.Runtime.Serialization.DataMember] 31 | public int id { get; set; } 32 | [System.Runtime.Serialization.DataMember] 33 | public string name { get; set; } 34 | } 35 | public class VOICEVOXEnumerator : ISpeechEnumerator 36 | { 37 | string[] _name = new string[0]; 38 | internal string BaseUrl; 39 | public string EngineName; 40 | 41 | public Dictionary Names = new Dictionary(); 42 | public VOICEVOXEnumerator() 43 | { 44 | Initialize("VOICEVOX","http://127.0.0.1:50021"); 45 | } 46 | 47 | public string AssemblyPath { get; private set; } 48 | internal void Initialize(string engineName,string baseUrl) 49 | { 50 | EngineName = engineName; 51 | BaseUrl = baseUrl; 52 | List presetName = new List(); 53 | try 54 | { 55 | using (var client = new HttpClient()) 56 | { 57 | client.Timeout = TimeSpan.FromSeconds(2); 58 | var response = client.GetAsync($"{baseUrl}/speakers").GetAwaiter().GetResult(); 59 | if (response.StatusCode == HttpStatusCode.OK) 60 | { 61 | var json = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); 62 | using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(json))) 63 | { 64 | var sr = new DataContractJsonSerializer(typeof(Speaker[])); 65 | var data = sr.ReadObject(ms) as Speaker[]; 66 | foreach (var d in data) 67 | { 68 | presetName.Add(d.name); 69 | Names.Add(d.name, d.styles[0].id); // スタイル省略時は各話者の最初のIDを利用する 70 | for (int i = 1; i < d.styles.Length; i++) // スタイルは(スタイル名)とする 71 | { 72 | string styleName = $"{d.name}({d.styles[i].name})"; 73 | presetName.Add(styleName); 74 | Names.Add(styleName, d.styles[i].id); 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | catch 82 | { 83 | // 何らかの例外が出た場合は無視 84 | } 85 | _name = presetName.ToArray(); 86 | } 87 | public SpeechEngineInfo[] GetSpeechEngineInfo() 88 | { 89 | List info = new List(); 90 | foreach (var v in _name) 91 | { 92 | info.Add(new SpeechEngineInfo 93 | { 94 | EngineName = EngineName, 95 | LibraryName = v, 96 | Is64BitProcess = Environment.Is64BitProcess 97 | }) ; 98 | } 99 | return info.ToArray(); 100 | } 101 | 102 | public virtual ISpeechController GetControllerInstance(SpeechEngineInfo info) 103 | { 104 | return EngineName == info.EngineName ? new VOICEVOXController(info) : null; 105 | } 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/Speech/Controller/Voiceroid2Controller.cs: -------------------------------------------------------------------------------- 1 | using Codeer.Friendly; 2 | using Codeer.Friendly.Windows; 3 | using Codeer.Friendly.Windows.Grasp; 4 | using RM.Friendly.WPFStandardControls; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel; 8 | using System.Diagnostics; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Runtime.InteropServices; 12 | using System.Text; 13 | using System.Threading; 14 | using System.Threading.Tasks; 15 | 16 | using System.Windows.Forms; 17 | using System.Windows.Threading; 18 | 19 | namespace Speech 20 | { 21 | /// 22 | /// VOICEROID2 操作クラス 23 | /// 24 | public class Voiceroid2Controller : IDisposable, ISpeechController 25 | { 26 | WindowsAppFriend _app; 27 | Process _process; 28 | WindowControl _root; 29 | System.Timers.Timer _timer; // 状態監視のためのタイマー 30 | Queue _queue = new Queue(); 31 | 32 | public delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lparam); 33 | static int _pid = 0; 34 | 35 | [DllImport("User32.dll")] 36 | static extern int SetForegroundWindow(IntPtr hWnd); 37 | [DllImport("user32.dll", CharSet = CharSet.Auto)] 38 | private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); 39 | 40 | public SpeechEngineInfo Info { get; private set; } 41 | 42 | /// 43 | /// Voiceroid のフルパス 44 | /// 45 | public string VoiceroidPath { get; private set; } 46 | 47 | string _libraryName; 48 | string _promptString; 49 | bool _isPlaying = false; 50 | bool _isRunning = false; 51 | double _tickCount = 0; 52 | public Voiceroid2Controller(SpeechEngineInfo info) 53 | { 54 | Info = info; 55 | 56 | var voiceroid2 = new Voiceroid2Enumerator(); 57 | _promptString = voiceroid2.PromptString; 58 | 59 | VoiceroidPath = info.EnginePath; 60 | _libraryName = info.LibraryName; 61 | _timer = new System.Timers.Timer(100); 62 | _timer.Elapsed += timer_Elapsed; 63 | } 64 | 65 | object _lockObject = new object(); 66 | 67 | private void timer_Elapsed(object sender, EventArgs e) 68 | { 69 | _timer.Stop(); // 途中の処理が重いため、タイマーをいったん止める 70 | lock (_lockObject) 71 | { 72 | _tickCount += _timer.Interval; 73 | 74 | // ここからプロセス間通信&UI操作(重い) 75 | WPFButtonBase playButton = new WPFButtonBase(_root.IdentifyFromLogicalTreeIndex(0, 4, 3, 5, 3, 0, 3, 0)); 76 | var d = playButton.LogicalTree(); 77 | System.Windows.Visibility v = (System.Windows.Visibility)(d[2])["Visibility"]().Core; // [再生]の画像の表示状態 78 | // ここまで 79 | 80 | if (v != System.Windows.Visibility.Visible && !_isRunning) 81 | { 82 | _isRunning = true; 83 | } 84 | else 85 | // 再生開始から 500 ミリ秒程度経過しても再生ボタンがうまく確認できなかった場合にも完了とみなす 86 | if (v == System.Windows.Visibility.Visible && (_isRunning || (!_isRunning && _tickCount > 500))) 87 | { 88 | if(_queue.Count == 0) 89 | { 90 | StopSpeech(); 91 | return; // タイマーが止まったまま終了 92 | }else 93 | { 94 | // 喋るべき内容が残っているときは再開 95 | string t = _queue.Dequeue(); 96 | WPFTextBox textbox = new WPFTextBox(_root.IdentifyFromLogicalTreeIndex(0, 4, 3, 5, 3, 0, 2)); 97 | textbox.EmulateChangeText(t); 98 | 99 | playButton.EmulateClick(); 100 | _isPlaying = true; 101 | _isRunning = false; 102 | _tickCount = 0; 103 | } 104 | } 105 | 106 | _timer.Start(); 107 | } 108 | } 109 | 110 | private void StopSpeech() 111 | { 112 | _timer.Stop(); 113 | lock (_lockObject) 114 | { 115 | _tickCount = 0; 116 | if (_isPlaying) 117 | { 118 | _isRunning = false; 119 | _isPlaying = false; 120 | OnFinished(); 121 | } 122 | } 123 | } 124 | 125 | /// 126 | /// 音声再生が完了したときに発生するイベント 127 | /// 128 | public event EventHandler Finished; 129 | protected virtual void OnFinished() 130 | { 131 | EventArgs se = new EventArgs(); 132 | Finished?.Invoke(this, se); 133 | } 134 | 135 | /// 136 | /// Voiceroid が起動中かどうかを確認 137 | /// 138 | /// 起動中であれば true 139 | public bool IsActive() 140 | { 141 | string name = Path.GetFileNameWithoutExtension(VoiceroidPath); 142 | Process[] localByName = Process.GetProcessesByName(name); 143 | 144 | if (localByName.Length > 0) 145 | { 146 | // VOICEROID2 は2重起動しないはずなので 0番目を参照する 147 | _process = localByName[0]; 148 | _pid = _process.Id; 149 | return true; 150 | } 151 | return false; 152 | } 153 | 154 | /// 155 | /// Voiceroidを起動する。すでに起動している場合には起動しているものを操作対象とする。 156 | /// 157 | public void Activate() 158 | { 159 | if (!IsActive()) 160 | { 161 | _app = new WindowsAppFriend(Process.Start(this.VoiceroidPath)); 162 | while (_root == null || (_root != null && _root.TypeFullName != "AI.Talk.Editor.MainWindow")) 163 | { 164 | _process = Process.GetProcessById(_app.ProcessId); 165 | _root = WindowControl.GetTopLevelWindows(_app)[0]; 166 | Thread.Sleep(2000); 167 | } 168 | }else 169 | { 170 | _app = new WindowsAppFriend(_process); 171 | _process = Process.GetProcessById(_app.ProcessId); 172 | _root = WindowControl.GetTopLevelWindows(_app)[0]; 173 | } 174 | } 175 | 176 | /// 177 | /// 指定した文字列を再生します 178 | /// 179 | /// 再生する文字列 180 | public void Play(string text) 181 | { 182 | SetText(text); 183 | } 184 | internal virtual void SetText(string text) 185 | { 186 | text = text.Trim() == "" ? "." : text; 187 | string t = _libraryName + _promptString + text; 188 | if (_queue.Count == 0) 189 | { 190 | WPFTextBox textbox = new WPFTextBox(_root.IdentifyFromLogicalTreeIndex(0, 4, 3, 5, 3, 0, 2)); 191 | textbox.EmulateChangeText(t); 192 | Play(); 193 | } 194 | else 195 | { 196 | _queue.Enqueue(t); 197 | } 198 | } 199 | 200 | /// 201 | /// VOICEROID2 に入力された文字列を再生します 202 | /// 203 | public void Play() 204 | { 205 | WPFButtonBase playButton = new WPFButtonBase(_root.IdentifyFromLogicalTreeIndex(0, 4, 3, 5, 3, 0, 3, 0)); 206 | playButton.EmulateClick(); 207 | Application.DoEvents(); 208 | _isPlaying = true; 209 | _isRunning = false; 210 | _timer.Start(); 211 | } 212 | /// 213 | /// VOICEROID2 の再生を停止します(停止ボタンを押す) 214 | /// 215 | public void Stop() 216 | { 217 | StopSpeech(); 218 | WPFButtonBase stopButton = new WPFButtonBase(_root.IdentifyFromLogicalTreeIndex(0, 4, 3, 5, 3, 0, 3, 1)); 219 | stopButton.EmulateClick(); 220 | } 221 | 222 | enum EffectType { Volume = 0, Speed = 1, Pitch = 2, PitchRange = 3} 223 | /// 224 | /// 音量を設定します 225 | /// 226 | /// 0.0~2.0 227 | public void SetVolume(float value) 228 | { 229 | SetEffect(EffectType.Volume, value); 230 | } 231 | /// 232 | /// 音量を取得します 233 | /// 234 | /// 音量 235 | public float GetVolume() 236 | { 237 | return GetEffect(EffectType.Volume); 238 | } 239 | /// 240 | /// 話速を設定します 241 | /// 242 | /// 0.5~4.0 243 | public void SetSpeed(float value) 244 | { 245 | SetEffect(EffectType.Speed, value); 246 | } 247 | /// 248 | /// 話速を取得します 249 | /// 250 | /// 話速 251 | public float GetSpeed() 252 | { 253 | return GetEffect(EffectType.Speed); 254 | } 255 | 256 | /// 257 | /// 高さを設定します 258 | /// 259 | /// 0.5~2.0 260 | public void SetPitch(float value) 261 | { 262 | SetEffect(EffectType.Pitch, value); 263 | } 264 | /// 265 | /// 高さを取得します 266 | /// 267 | /// 高さ 268 | public float GetPitch() 269 | { 270 | return GetEffect(EffectType.Pitch); 271 | } 272 | /// 273 | /// 抑揚を設定します 274 | /// 275 | /// 0.0~2.0 276 | public void SetPitchRange(float value) 277 | { 278 | SetEffect(EffectType.PitchRange, value); 279 | } 280 | /// 281 | /// 抑揚を取得します 282 | /// 283 | /// 抑揚 284 | public float GetPitchRange() 285 | { 286 | return GetEffect(EffectType.PitchRange); 287 | } 288 | 289 | private void SetEffect(EffectType t, float value) 290 | { 291 | WPFTextBox textbox = new WPFTextBox(_root.IdentifyFromLogicalTreeIndex(0, 4, 5, 0, 1, 0, 3, 0, 6, (int)t, 0, 7)); 292 | textbox.EmulateChangeText($"{value:0.00}"); 293 | } 294 | private float GetEffect(EffectType t) 295 | { 296 | WPFTextBox textbox = new WPFTextBox(_root.IdentifyFromLogicalTreeIndex(0, 4, 5, 0, 1, 0, 3, 0, 6, (int)t, 0, 7)); 297 | return Convert.ToSingle(textbox.Text); 298 | } 299 | 300 | #region IDisposable Support 301 | private bool disposedValue = false; 302 | 303 | protected virtual void Dispose(bool disposing) 304 | { 305 | if (!disposedValue) 306 | { 307 | if (disposing) 308 | { 309 | if(_app != null) 310 | { 311 | _app.Dispose(); 312 | } 313 | } 314 | disposedValue = true; 315 | } 316 | } 317 | 318 | public void Dispose() 319 | { 320 | Dispose(true); 321 | } 322 | #endregion 323 | } 324 | } -------------------------------------------------------------------------------- /src/Speech/Controller/Voiceroid2Enumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Xml.Linq; 8 | 9 | namespace Speech 10 | { 11 | public class Voiceroid2Enumerator : ISpeechEnumerator 12 | { 13 | protected string[] _name = new string[0]; 14 | public string PromptString { get; internal set; } 15 | 16 | public string EngineName { get; internal set; } 17 | public Voiceroid2Enumerator() 18 | { 19 | // VOICEROID2 の一覧は下記で取得できる 20 | // 下記ファイルは VOICEROID2 終了時に生成されるため、一度 VOICEROID2 を起動・終了 21 | // しておくこと 22 | string path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) 23 | + @"\AHS\VOICEROID\2.0\Standard.settings"; 24 | Initialize(path, "VOICEROID2"); 25 | } 26 | 27 | internal void Initialize(string path,string engineName) 28 | { 29 | EngineName = engineName; 30 | 31 | if (File.Exists(path)) 32 | { 33 | List presetName = new List(); 34 | try 35 | { 36 | var xml = XElement.Load(path); 37 | 38 | // 話者を識別するための記号。デフォルトは「>」。「紲星あかり>」などと指定する。 39 | PromptString = (from c in xml.Elements("VoicePreset").Elements("PromptString") 40 | select c.Value).ToArray()[0]; 41 | 42 | // インストール済み話者一覧 43 | presetName.AddRange(from c in xml.Elements("VoicePreset").Elements("VoicePresets").Elements("VoicePreset").Elements("PresetName") 44 | select c.Value); 45 | 46 | // ユーザが追加・変更した話者一覧 47 | string isSpecialFolderEnabled = (from c in xml.Elements("VoicePreset").Elements("VoicePresetFilePath").Elements("IsSpecialFolderEnabled") 48 | select c.Value).ToArray()[0]; 49 | string partialPath = (from c in xml.Elements("VoicePreset").Elements("VoicePresetFilePath").Elements("PartialPath") 50 | select c.Value).ToArray()[0]; 51 | string userPresetPath = Path.Combine( 52 | Environment.GetFolderPath(Environment.SpecialFolder.Personal) 53 | , partialPath); 54 | if (isSpecialFolderEnabled == "false") 55 | { 56 | userPresetPath = partialPath; 57 | } 58 | var userXml = XElement.Load(userPresetPath); 59 | presetName.AddRange(from c in userXml.Elements("VoicePreset").Elements("PresetName") 60 | select c.Value); 61 | 62 | _name = presetName.ToArray(); 63 | } 64 | catch 65 | { 66 | PromptString = ""; 67 | } 68 | } 69 | else 70 | { 71 | _name = new string[0]; 72 | } 73 | } 74 | public virtual SpeechEngineInfo[] GetSpeechEngineInfo() 75 | { 76 | List info = new List(); 77 | string path = GetInstalledPath(); 78 | 79 | if (string.IsNullOrEmpty(path)) 80 | { 81 | return new SpeechEngineInfo[0]; 82 | } 83 | foreach (var v in _name) 84 | { 85 | info.Add(new SpeechEngineInfo { EngineName = EngineName, EnginePath = path, LibraryName = v }); 86 | } 87 | return info.ToArray(); 88 | } 89 | internal virtual string GetInstalledPath() 90 | { 91 | string uninstall_path = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\"; 92 | // 32bit の場合 SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; 93 | 94 | string result = ""; 95 | Microsoft.Win32.RegistryKey uninstall = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(uninstall_path, false); 96 | if (uninstall != null) 97 | { 98 | foreach (string subKey in uninstall.GetSubKeyNames()) 99 | { 100 | Microsoft.Win32.RegistryKey appkey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(uninstall_path + "\\" + subKey, false); 101 | var key = appkey.GetValue("DisplayName"); 102 | if (key != null && key.ToString() == "VOICEROID2 Editor") 103 | { 104 | var location = appkey.GetValue("InstallLocation").ToString(); 105 | result = Path.Combine(location , @"VoiceroidEditor.exe"); 106 | break; 107 | } 108 | } 109 | } 110 | return result; 111 | } 112 | 113 | 114 | public virtual ISpeechController GetControllerInstance(SpeechEngineInfo info) 115 | { 116 | return EngineName == info.EngineName ? new Voiceroid2Controller(info) : null; 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/Speech/Controller/Voiceroid64Enumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Speech 9 | { 10 | class Voiceroid64Enumerator : Voiceroid2Enumerator 11 | { 12 | public Voiceroid64Enumerator() 13 | { 14 | string path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) 15 | + @"\AHS\VOICEROID\2.0\Standard.settings"; 16 | Initialize(path, "VOICEROID64"); 17 | } 18 | 19 | internal override string GetInstalledPath() 20 | { 21 | string uninstall_path = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"; 22 | 23 | string result = ""; 24 | Microsoft.Win32.RegistryKey uninstall = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(uninstall_path, false); 25 | if (uninstall != null) 26 | { 27 | foreach (string subKey in uninstall.GetSubKeyNames()) 28 | { 29 | Microsoft.Win32.RegistryKey appkey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(uninstall_path + "\\" + subKey, false); 30 | var key = appkey.GetValue("DisplayName"); 31 | if (key != null && key.ToString() == "VOICEROID2 Editor 64bit") 32 | { 33 | var location = appkey.GetValue("InstallLocation").ToString(); 34 | result = Path.Combine(location, @"VoiceroidEditor.exe"); 35 | break; 36 | } 37 | } 38 | } 39 | 40 | return result; 41 | } 42 | 43 | public override SpeechEngineInfo[] GetSpeechEngineInfo() 44 | { 45 | var info = base.GetSpeechEngineInfo(); 46 | foreach(var i in info) 47 | { 48 | i.Is64BitProcess = true; 49 | } 50 | return info; 51 | } 52 | 53 | public override ISpeechController GetControllerInstance(SpeechEngineInfo info) 54 | { 55 | return EngineName == info.EngineName ? new Voiceroid64Controller(info) : null; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Speech/Controller/VoiceroidPlusController.cs: -------------------------------------------------------------------------------- 1 | using Codeer.Friendly; 2 | using Codeer.Friendly.Windows; 3 | using Codeer.Friendly.Windows.Grasp; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Diagnostics; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Runtime.InteropServices; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | using System.Windows.Forms; 14 | using System.Windows.Threading; 15 | 16 | namespace Speech 17 | { 18 | /// 19 | /// VOICEROID+ 操作クラス 20 | /// 21 | public class VoiceroidPlusController : IDisposable, ISpeechController 22 | { 23 | WindowsAppFriend _app; 24 | Process _process; 25 | protected WindowControl _root; 26 | 27 | System.Timers.Timer _timer; // 状態監視のためのタイマー 28 | bool _playStarting = false; 29 | 30 | [DllImport("User32.dll")] 31 | static extern int SetForegroundWindow(IntPtr hWnd); 32 | [DllImport("user32.dll", CharSet = CharSet.Auto)] 33 | protected static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); 34 | 35 | 36 | /// 37 | /// Voiceroid のフルパス 38 | /// 39 | public string VoiceroidPath { get; protected set; } 40 | 41 | public SpeechEngineInfo Info { get; protected set; } 42 | 43 | public VoiceroidPlusController(SpeechEngineInfo info) 44 | { 45 | Info = info; 46 | VoiceroidPath = info.EnginePath; 47 | _timer = new System.Timers.Timer(100); 48 | _timer.Elapsed += timer_Elapsed; 49 | } 50 | 51 | private void timer_Elapsed(object sender, EventArgs e) 52 | { 53 | try 54 | { 55 | WindowControl playButton = _root.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 0, 3); 56 | AppVar button = playButton.AppVar; 57 | string text = (string)button["Text"]().Core; 58 | if (!_playStarting && text.Trim() == "再生") 59 | { 60 | _timer.Stop(); 61 | OnFinished(); 62 | } 63 | _playStarting = false; 64 | } 65 | catch 66 | { 67 | // VOICEROID+ との通信が失敗することがあるが無視 68 | } 69 | } 70 | 71 | /// 72 | /// 音声再生が完了したときに発生するイベント 73 | /// 74 | public event EventHandler Finished; 75 | protected virtual void OnFinished() 76 | { 77 | EventArgs se = new EventArgs(); 78 | Finished?.Invoke(this, se); 79 | } 80 | 81 | /// 82 | /// Voiceroid が起動中かどうかを確認 83 | /// 84 | /// 起動中であれば true 85 | public bool IsActive() 86 | { 87 | string name = Path.GetFileNameWithoutExtension(VoiceroidPath); 88 | Process[] localByName = Process.GetProcessesByName(name); 89 | foreach(var p in localByName) 90 | { 91 | if(p.MainModule.FileName == VoiceroidPath) 92 | { 93 | _process = p; 94 | return true; 95 | } 96 | } 97 | return false; 98 | } 99 | 100 | /// 101 | /// Voiceroidを起動する。すでに起動している場合には起動しているものを操作対象とする。 102 | /// 103 | public void Activate() 104 | { 105 | if (IsActive()) 106 | { 107 | _app = new WindowsAppFriend(_process); 108 | } 109 | else 110 | { 111 | _process = Process.Start(VoiceroidPath); 112 | _app = new WindowsAppFriend(_process); 113 | } 114 | _root = WindowControl.GetTopLevelWindows(_app)[0]; 115 | } 116 | 117 | /// 118 | /// 指定した文字列を再生します 119 | /// 120 | /// 再生する文字列 121 | public void Play(string text) 122 | { 123 | WindowControl speechTextBox = _root.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 1); 124 | AppVar textbox = speechTextBox.AppVar; 125 | textbox["Text"](text); 126 | Play(); 127 | } 128 | /// 129 | /// VOICEROID+ に入力された文字列を再生します 130 | /// 131 | public virtual void Play() 132 | { 133 | WindowControl playButton = _root.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 0, 3); 134 | AppVar button = playButton.AppVar; 135 | string text = (string)button["Text"]().Core; 136 | if(text.Trim() == "再生") 137 | { 138 | button["PerformClick"](); 139 | _playStarting = true; 140 | _timer.Start(); 141 | } 142 | } 143 | /// 144 | /// VOICEROID+ の再生を停止します(停止ボタンを押す) 145 | /// 146 | public virtual void Stop() 147 | { 148 | WindowControl stopButton = _root.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 0, 2); 149 | AppVar button = stopButton.AppVar; 150 | button["PerformClick"](); 151 | } 152 | 153 | protected enum EffectType { Volume = 8, Speed = 9, Pitch = 10, PitchRange = 11} 154 | /// 155 | /// 音量を設定します 156 | /// 157 | /// 0.0~2.0 158 | public void SetVolume(float value) 159 | { 160 | SetEffect(EffectType.Volume, value); 161 | } 162 | /// 163 | /// 音量を取得します 164 | /// 165 | /// 音量 166 | public float GetVolume() 167 | { 168 | return GetEffect(EffectType.Volume); 169 | } 170 | /// 171 | /// 話速を設定します 172 | /// 173 | /// 0.5~4.0 174 | public void SetSpeed(float value) 175 | { 176 | SetEffect(EffectType.Speed,value); 177 | } 178 | /// 179 | /// 話速を取得します 180 | /// 181 | /// 話速 182 | public float GetSpeed() 183 | { 184 | return GetEffect(EffectType.Speed); 185 | } 186 | 187 | /// 188 | /// 高さを設定します 189 | /// 190 | /// 0.5~2.0 191 | public void SetPitch(float value) 192 | { 193 | SetEffect(EffectType.Pitch, value); 194 | ChangeToVoiceEffect(); 195 | } 196 | /// 197 | /// 高さを取得します 198 | /// 199 | /// 高さ 200 | public float GetPitch() 201 | { 202 | return GetEffect(EffectType.Pitch); 203 | } 204 | /// 205 | /// 抑揚を設定します 206 | /// 207 | /// 0.0~2.0 208 | public void SetPitchRange(float value) 209 | { 210 | SetEffect(EffectType.PitchRange, value); 211 | } 212 | /// 213 | /// 抑揚を取得します 214 | /// 215 | /// 抑揚 216 | public float GetPitchRange() 217 | { 218 | return GetEffect(EffectType.PitchRange); 219 | } 220 | 221 | protected virtual void SetEffect(EffectType t, float value) 222 | { 223 | ChangeToVoiceEffect(); 224 | int index = (int)t; 225 | WindowControl control = _root.IdentifyFromZIndex(2, 0, 0, 0, 0, 0, 0, index); 226 | AppVar v = control.AppVar; 227 | v["Focus"](); 228 | v["Text"](string.Format("{0:0.00}", value)); 229 | 230 | // TODO: VOICEROID+では数値を変更するだけでは変更が行われないため何らかの方法が必要 231 | 232 | } 233 | protected virtual float GetEffect(EffectType t) 234 | { 235 | ChangeToVoiceEffect(); 236 | int index = (int)t; 237 | WindowControl control = _root.IdentifyFromZIndex(2, 0, 0, 0, 0, 0, 0, index); 238 | AppVar v = control.AppVar; 239 | return Convert.ToSingle((string)v["Text"]().Core); 240 | } 241 | 242 | 243 | /// 244 | /// 音声効果タブを選択します 245 | /// 246 | protected virtual void ChangeToVoiceEffect() 247 | { 248 | RestoreMinimizedWindow(); 249 | WindowControl tabControl = _root.IdentifyFromZIndex(2, 0, 0, 0, 0); 250 | AppVar tab = tabControl.AppVar; 251 | tab["SelectedIndex"](2); 252 | } 253 | protected void RestoreMinimizedWindow() 254 | { 255 | const uint WM_SYSCOMMAND = 0x0112; 256 | const int SC_RESTORE = 0xF120; 257 | FormWindowState state = (FormWindowState)_root["WindowState"]().Core; 258 | if (state == FormWindowState.Minimized) 259 | { 260 | SendMessage(_root.Handle, WM_SYSCOMMAND, 261 | new IntPtr(SC_RESTORE), IntPtr.Zero); 262 | } 263 | } 264 | 265 | #region IDisposable Support 266 | private bool disposedValue = false; 267 | 268 | protected virtual void Dispose(bool disposing) 269 | { 270 | if (!disposedValue) 271 | { 272 | if (disposing) 273 | { 274 | if(_app != null) 275 | { 276 | _app.Dispose(); 277 | } 278 | } 279 | disposedValue = true; 280 | } 281 | } 282 | 283 | public void Dispose() 284 | { 285 | Dispose(true); 286 | } 287 | #endregion 288 | } 289 | } -------------------------------------------------------------------------------- /src/Speech/Controller/VoiceroidPlusEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Xml.Linq; 8 | 9 | namespace Speech 10 | { 11 | 12 | public class VoiceroidPlusEnumerator : ISpeechEnumerator 13 | { 14 | 15 | protected class Data 16 | { 17 | public string Name { get; internal set; } 18 | public string Path { get; internal set; } 19 | } 20 | protected Data[] _info; 21 | public string EngineName { get; internal set; } 22 | 23 | public VoiceroidPlusEnumerator() 24 | { 25 | Initialize(); 26 | } 27 | 28 | private void Initialize() 29 | { 30 | EngineName = "VOICEROID+"; 31 | // VOICEROID の一覧は下記で取得できる 32 | string path = System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) 33 | + @"\AHS\"; 34 | List data = new List(); 35 | try 36 | { 37 | string[] files = Directory.GetDirectories(path); 38 | 39 | for (int i = 0; i < files.Length; i++) 40 | { 41 | string folder = files[i].Substring(files[i].LastIndexOf(@"\")); 42 | if (folder.StartsWith(@"\VOICEROID+")) 43 | { 44 | Data d = new Data(); 45 | d.Name = folder.Substring(12); // 「東北きりたん」など 46 | 47 | string[] sub = Directory.GetDirectories(files[i]); 48 | var xml = XElement.Load(Path.Combine(sub[0], "VOICEROID.dat")); 49 | var dbsPath = (from c in xml.Elements("DbsPath") 50 | select c.Value).ToArray()[0]; 51 | d.Path = Path.Combine(dbsPath.Substring(0, dbsPath.LastIndexOf(@"\")), "VOICEROID.exe"); 52 | 53 | data.Add(d); 54 | } 55 | } 56 | } 57 | catch 58 | { 59 | // 初期化に途中で失敗した場合はうまく処理できたところまで返す 60 | } 61 | _info = data.ToArray(); 62 | } 63 | 64 | public SpeechEngineInfo[] GetSpeechEngineInfo() 65 | { 66 | 67 | List info = new List(); 68 | foreach (var v in _info) 69 | { 70 | info.Add(new SpeechEngineInfo { EngineName = EngineName, EnginePath = v.Path, LibraryName = v.Name }); 71 | } 72 | return info.ToArray(); 73 | } 74 | public virtual ISpeechController GetControllerInstance(SpeechEngineInfo info) 75 | { 76 | return EngineName == info.EngineName ? new VoiceroidPlusController(info) : null; 77 | } 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Speech/Effect/Wave.cs: -------------------------------------------------------------------------------- 1 | using NAudio.Wave; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Speech.Effect 9 | { 10 | /// 11 | /// Waveファイル操作 12 | /// 13 | public class Wave 14 | { 15 | /// 16 | /// 波形データ 17 | /// 18 | public double[] Data { get; set; } 19 | public double[] EData { get; set; } 20 | public WaveFormat Format { get; private set; } 21 | /// 22 | /// 音声データを読み込みます 23 | /// 24 | /// ファイル名 25 | /// 読み込んだデータをdouble[](-1.0~1.0)に変換したもの 26 | public double[] Read(string filename) 27 | { 28 | Data = null; 29 | 30 | Format = new WaveFormat(48000, 16, 1); 31 | string tmpFile = "resampled.wav"; 32 | using (WaveFileReader reader = new WaveFileReader(filename)) 33 | { 34 | using (var resampler = new MediaFoundationResampler(reader, Format)) 35 | { 36 | WaveFileWriter.CreateWaveFile(tmpFile, resampler); 37 | } 38 | } 39 | using (WaveFileReader reader = new WaveFileReader(tmpFile)) 40 | { 41 | byte[] src = new byte[reader.Length]; 42 | reader.Read(src, 0, src.Length); 43 | Data = ConvertToDouble(src); 44 | } 45 | 46 | return Data; 47 | } 48 | /// 49 | /// 音声データをファイルに出力します 50 | /// 51 | /// 出力ファイル名 52 | /// 音声データ 53 | public void Write(string filename, double[] data) 54 | { 55 | using (WaveFileWriter writer = new WaveFileWriter(filename, Format)) 56 | { 57 | float scale = 5f; 58 | writer.Write(ConvertToByte(data, scale), 0, data.Length * 2); 59 | } 60 | } 61 | 62 | short nonzero = 1; 63 | 64 | private double[] ConvertToDouble(byte[] data) 65 | { 66 | double[] result = new double[data.Length / 2]; 67 | for (int i = 0; i < data.Length; i += 2) 68 | { 69 | short d = (short)(data[i] | (data[i + 1] << 8)); 70 | if (d == 0) 71 | { 72 | d = nonzero; // 信号レベルが0の状態が続くと以降が無音になってしまうための対応 73 | } 74 | result[i / 2] = d / 32767.0; 75 | } 76 | return result; 77 | } 78 | private byte[] ConvertToByte(double[] data, float scale) 79 | { 80 | byte[] result = new byte[data.Length * 2]; 81 | for (int i = 0; i < data.Length; i++) 82 | { 83 | short d = (short)(data[i] * 32767.0 * scale); 84 | if (d == nonzero) 85 | { 86 | d = 0; 87 | } 88 | result[i * 2] = (byte)(d & 255); 89 | result[i * 2 + 1] = (byte)((d >> 8) & 255); 90 | } 91 | return result; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Speech/Effect/Whisper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Speech.Effect 8 | { 9 | /// 10 | /// toWhisper: (c) zeta, 2017 (修正BSDライセンス) 11 | /// https://github.com/zeta-chicken/toWhisper 12 | /// を元に C# に移植 13 | /// https://github.com/ksasao/toWhisper 14 | /// 15 | public class Whisper 16 | { 17 | //LPC次数 18 | public int Order { get; set; } = 0; 19 | //有声音割合(0.0~1.0) 20 | public double Rate { get; set; } = 0.02; 21 | //プリエンファシスフィルタの係数(0.0~1.0) 22 | public double Hpf { get; set; } = 0.97; 23 | //デエンファシスフィルタの係数(0.0~1.0) 24 | public double Lpf { get; set; } = 0.2; 25 | 26 | //フレーム幅をサンプル数に 27 | public double FrameT { get; set; } = 20.0; 28 | int frame = 0; 29 | 30 | // ホワイトノイズ生成用 31 | Random random = new Random(); 32 | 33 | 34 | public void Convert(Wave wave) 35 | { 36 | AdjustSize(wave); 37 | WhisperFilter(wave); 38 | } 39 | 40 | private void WhisperFilter(Wave wave) 41 | { 42 | Func windowFunction = Hamming; 43 | int length = wave.Data.Length; 44 | double[] x = new double[frame]; 45 | double[] y = new double[frame]; 46 | double[] E = new double[frame]; //残差信号 47 | double[] a = new double[Order + 1]; //LPC係数 48 | 49 | double[] v = wave.Data; 50 | double[] v1 = new double[v.Length]; 51 | double[] v2 = new double[v.Length]; 52 | 53 | for (int i = 0; i < length / frame * 2 - 1; i++) 54 | { 55 | double max = 0.0; 56 | for (int j = 0; j < frame; j++) 57 | { 58 | x[j] = v[j + i * frame / 2] * windowFunction(j, frame); 59 | } 60 | 61 | //LPC係数の導出 62 | LevinsonDurbin(x, frame, a, Order); 63 | 64 | //残差信号(声帯音源)の導出 65 | for (int j = 0; j < frame; j++) 66 | { 67 | double e = 0.0; 68 | for (int n = 0; n < Order + 1; n++) 69 | { 70 | if (j >= n) 71 | { 72 | e += a[n] * x[j - n]; 73 | } 74 | } 75 | E[j] = e; 76 | max += e * e; 77 | } 78 | 79 | //声帯振動 80 | for (int j = 0; j < frame; j++) 81 | { 82 | v2[j + i * frame / 2] += E[j] * 10.0; 83 | } 84 | //ホワイトノイズの生成 85 | for (int j = 0; j < frame; j++) 86 | { 87 | y[j] = GenerateWhiteNoise(); 88 | } 89 | 90 | for (int j = 1; j < frame; j++) 91 | { 92 | E[j] = ((1.0 - Rate) * E[j - 1] + E[j]) / (2.0 - Rate); 93 | } 94 | //残差信号とノイズの二乗平均レベルをそろえる 95 | max = Math.Sqrt(3.0 * max / frame); 96 | for (int j = 0; j < frame; j++) 97 | { 98 | y[j] = Rate * E[j] + (1.0 - Rate) * max * y[j]; 99 | } 100 | 101 | //for (int j=1; j= n) y[j] -= a[n] * y[j - n]; 109 | } 110 | } 111 | 112 | for (int j = 0; j < frame; j++) 113 | { 114 | v1[j + i * frame / 2] += y[j]; 115 | } 116 | } 117 | 118 | //デエンファシス 119 | for (int i = 1; i < length; i++) v1[i] = Lpf * v1[i - 1] + v1[i]; 120 | for (int i = 1; i < length; i++) v2[i] = Lpf * v2[i - 1] + v2[i]; 121 | 122 | wave.Data = v1; 123 | wave.EData = v2; 124 | } 125 | 126 | /// 127 | /// フィルタ処理に適したサイズに調整する 128 | /// 129 | /// 読み込んだWaveデータ 130 | private void AdjustSize(Wave wave) 131 | { 132 | //LPC次数の計算 133 | if (Order == 0) 134 | { 135 | Order = wave.Format.SampleRate * 40 / 44100; 136 | } 137 | 138 | //20msのフレーム幅 139 | frame = (int)(wave.Format.SampleRate * FrameT / 1000); 140 | if (frame % 2 != 0) frame++; 141 | 142 | //フレーム幅でちょうど割り切れるようにする 143 | int last = wave.Data.Length; 144 | int length = wave.Data.Length - (wave.Data.Length % frame) + frame; 145 | 146 | double[] v1 = wave.Data; 147 | double[] v = new double[length]; 148 | 149 | //足りない分はゼロづめ 150 | for (int i = 1; i < length; i++) 151 | { 152 | if (i < last) 153 | { 154 | v[i] = v1[i] - Hpf * v1[i - 1]; 155 | } 156 | else 157 | { 158 | v[i] = 0.0; 159 | } 160 | } 161 | 162 | wave.Data = v; 163 | } 164 | 165 | double Hanning(int i, int frame) 166 | { 167 | return 0.5 - 0.5 * Math.Cos(2.0 * Math.PI / (double)frame * (double)i); 168 | } 169 | 170 | double Hamming(int i, int frame) 171 | { 172 | return 0.54 - 0.46 * Math.Cos(2.0 * Math.PI / (double)frame * (double)i); 173 | } 174 | 175 | double Blackman(int i, int frame) 176 | { 177 | return 0.42 - 0.5 * Math.Cos(2.0 * Math.PI / (double)frame * (double)i) + 0.08 * Math.Cos(4.0 * Math.PI / (double)frame * (double)i); 178 | } 179 | 180 | // 自己相関関数 181 | double AutoCorrelation(double[] x, int l, int N) 182 | { 183 | double res = 0.0; 184 | double r = 0.0, t = 0.0; 185 | for (int i = 0; i < N - l; i++) 186 | { 187 | t = res + (x[i] * x[i + l] + r); 188 | r = (x[i] * x[i + l] + r) - (t - res); 189 | res = t; 190 | } 191 | return res; 192 | } 193 | void LevinsonDurbin(double[] x, int length, double[] a, int lpcOrder) 194 | { 195 | //lpcOrder = k の場合 196 | //フィルタ係数は 197 | //a[0], a[1] , ......, a[k] 198 | //となるため,k+1のメモリ領域を確保して置く必要がある. 199 | double lambda = 0.0, E = 0.0; 200 | double[] r = new double[lpcOrder + 1]; 201 | double[] V = new double[lpcOrder + 1]; 202 | double[] U = new double[lpcOrder + 1]; 203 | for (int i = 0; i < lpcOrder + 1; i++) 204 | { 205 | r[i] = AutoCorrelation(x, i, length); 206 | } 207 | for (int i = 0; i <= lpcOrder; i++) 208 | { 209 | a[i] = 0.0; 210 | } 211 | a[0] = 1.0; 212 | a[1] = -r[1] / r[0]; 213 | E = r[0] + r[1] * a[1]; 214 | for (int k = 1; k < lpcOrder; k++) 215 | { 216 | lambda = 0.0; 217 | for (int j = 0; j <= k; j++) 218 | { 219 | lambda += a[j] * r[k + 1 - j]; 220 | } 221 | lambda /= -E; 222 | for (int j = 0; j <= k + 1; j++) 223 | { 224 | U[j] = a[j]; 225 | V[j] = a[k + 1 - j]; 226 | } 227 | for (int j = 0; j <= k + 1; j++) 228 | { 229 | a[j] = U[j] + lambda * V[j]; 230 | } 231 | E = (1.0 - lambda * lambda) * E; 232 | } 233 | return; 234 | } 235 | double GenerateWhiteNoise() 236 | { 237 | return random.NextDouble() * 2.0 - 1.0; 238 | } 239 | 240 | void LtiFilter(double[] x, int len, double[] a, int al, double[] b, int bl) 241 | { 242 | //線形時不変フィルタ 243 | //a[0]は出力信号のフィルタ係数であるため,常に1にすること 244 | if (x == null) return; 245 | if (a == null) return; 246 | if (b == null) bl = 0; 247 | double[] y = new double[len]; 248 | 249 | y[0] = x[0]; 250 | for (int i = 1; i < len; i++) 251 | { 252 | y[i] = 0.0; 253 | for (int j = 0; j < bl; j++) 254 | { 255 | if (i >= j) y[i] += b[j] * x[i - j]; 256 | } 257 | for (int j = 1; j < al; j++) 258 | { 259 | if (i >= j) y[i] -= a[j] * y[i - j]; 260 | } 261 | } 262 | Array.Copy(y, x, len); 263 | return; 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/Speech/EngineParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Speech 8 | { 9 | public class EngineParameters 10 | { 11 | public float Pitch { get; set; } = -1; 12 | public float PitchRange { get; set; } = -1; 13 | public float Volume { get; set; } = -1; 14 | public float Speed { get; set; } = -1; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Speech/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 6 | // アセンブリに関連付けられている情報を変更するには、 7 | // これらの属性値を変更してください。 8 | [assembly: AssemblyTitle("Voice")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Voice")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから 18 | // 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、 19 | // その型の ComVisible 属性を true に設定してください。 20 | [assembly: ComVisible(false)] 21 | 22 | // このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります 23 | [assembly: Guid("7787a468-a1bb-4077-85bc-543258610060")] 24 | 25 | // アセンブリのバージョン情報は次の 4 つの値で構成されています: 26 | // 27 | // メジャー バージョン 28 | // マイナー バージョン 29 | // ビルド番号 30 | // Revision 31 | // 32 | // すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を 33 | // 既定値にすることができます: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/Speech/SoundPlayer.cs: -------------------------------------------------------------------------------- 1 | using NAudio.Wave; 2 | using NAudio.Wave.SampleProviders; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace Speech 12 | { 13 | public class SoundPlayer : IDisposable 14 | { 15 | private bool disposedValue; 16 | private WaveOutEvent waveOut; 17 | public SoundPlayer() 18 | { 19 | waveOut = new WaveOutEvent(); 20 | waveOut.PlaybackStopped += WaveOut_PlaybackStopped; 21 | } 22 | 23 | private void WaveOut_PlaybackStopped(object sender, StoppedEventArgs e) 24 | { 25 | // throw new NotImplementedException(); 26 | } 27 | 28 | 29 | /// 30 | /// 無音をスピーカーに出力します。Bluetoothスピーカーなど停止状態から 31 | /// 音声が正常に再生されるようになるまで一定時間音声出力が必要なもの 32 | /// のために利用します。 33 | /// 34 | /// 無音出力時間(ミリ秒) 35 | public void PlaySilenceMs(int millisec) 36 | { 37 | byte[] data = new byte[millisec*10*2]; 38 | IWaveProvider provider = new RawSourceWaveStream( 39 | new MemoryStream(data), new WaveFormat(10000,16,1)); 40 | 41 | waveOut.Init(provider); 42 | waveOut.Play(); 43 | while (waveOut.PlaybackState == PlaybackState.Playing) 44 | { 45 | Thread.Sleep(1); 46 | } 47 | waveOut.Stop(); 48 | } 49 | 50 | /// 51 | /// 音声ファイルを再生します。 52 | /// 53 | /// ファイル名 54 | public void Play(string filename) 55 | { 56 | using (var soundReader = new MediaFoundationReader(filename)) 57 | { 58 | waveOut.Init(soundReader); 59 | waveOut.Play(); 60 | while (waveOut.PlaybackState == PlaybackState.Playing) 61 | { 62 | Thread.Sleep(1); 63 | } 64 | } 65 | waveOut.Stop(); 66 | } 67 | 68 | protected virtual void Dispose(bool disposing) 69 | { 70 | if (!disposedValue) 71 | { 72 | if (disposing) 73 | { 74 | waveOut.Dispose(); 75 | } 76 | 77 | // TODO: アンマネージド リソース (アンマネージド オブジェクト) を解放し、ファイナライザーをオーバーライドします 78 | // TODO: 大きなフィールドを null に設定します 79 | disposedValue = true; 80 | } 81 | } 82 | 83 | 84 | public void Dispose() 85 | { 86 | // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します 87 | Dispose(disposing: true); 88 | GC.SuppressFinalize(this); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Speech/SoundRecorder.cs: -------------------------------------------------------------------------------- 1 | using NAudio.CoreAudioApi; 2 | using NAudio.Wave; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace Speech 13 | { 14 | public class SoundRecorder : IDisposable 15 | { 16 | private const int APPCOMMAND_VOLUME_MUTE = 0x80000; 17 | private const int APPCOMMAND_VOLUME_UP = 0xA0000; 18 | private const int APPCOMMAND_VOLUME_DOWN = 0x90000; 19 | private const int WM_APPCOMMAND = 0x319; 20 | 21 | bool _finished = false; 22 | 23 | [DllImport("kernel32.dll")] 24 | static extern IntPtr GetConsoleWindow(); 25 | [DllImport("user32.dll")] 26 | public static extern IntPtr SendMessageW(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); 27 | 28 | public IntPtr GetHandle() 29 | { 30 | var handle = Process.GetCurrentProcess().MainWindowHandle; 31 | if(handle == IntPtr.Zero) 32 | { 33 | handle = GetConsoleWindow(); 34 | } 35 | return handle; 36 | } 37 | 38 | private void Mute() 39 | { 40 | var handle = GetHandle(); 41 | SendMessageW(handle, WM_APPCOMMAND, handle, (IntPtr)APPCOMMAND_VOLUME_MUTE); 42 | } 43 | private void Unmute() 44 | { 45 | var handle = GetHandle(); 46 | SendMessageW(handle, WM_APPCOMMAND, handle, (IntPtr)APPCOMMAND_VOLUME_UP); 47 | SendMessageW(handle, WM_APPCOMMAND, handle, (IntPtr)APPCOMMAND_VOLUME_DOWN); 48 | } 49 | 50 | private WaveFileWriter _writer = null; 51 | private IWaveIn _capture = null; 52 | private bool disposedValue; 53 | 54 | /// 55 | /// 音声合成の完了後に何ミリ秒待ってから録音を終了するか 56 | /// 57 | public UInt32 PostWait { get; set; } = 0; 58 | 59 | /// 60 | /// Start()が呼び出されてから何ミリ秒待ってから録音を開始するか 61 | /// 62 | public UInt32 PreWait { get; set; } = 0; 63 | 64 | /// 65 | /// 出力先のファイル名を取得または設定します 66 | /// 67 | public string OutputPath { get; set; } 68 | 69 | public SoundRecorder(string filename) 70 | { 71 | OutputPath = filename; 72 | _capture = new WasapiLoopbackCapture(); 73 | } 74 | public async Task Start() 75 | { 76 | _finished = false; 77 | _writer = new WaveFileWriter(OutputPath, _capture.WaveFormat); 78 | _capture.DataAvailable += (s, a) => 79 | { 80 | _writer.Write(a.Buffer, 0, a.BytesRecorded); 81 | }; 82 | _capture.RecordingStopped += (s, a) => 83 | { 84 | _writer.Flush(); 85 | _writer.Close(); 86 | _writer.Dispose(); 87 | _finished = true; 88 | }; 89 | await Task.Delay((int)PreWait); 90 | Mute(); 91 | _capture.StartRecording(); 92 | } 93 | public async Task Stop() 94 | { 95 | if(_capture != null) 96 | { 97 | await Task.Delay((int)PostWait); 98 | _capture.StopRecording(); 99 | _capture.Dispose(); 100 | while (!_finished) 101 | { 102 | Thread.Sleep(100); 103 | } 104 | Unmute(); 105 | } 106 | } 107 | 108 | protected virtual void Dispose(bool disposing) 109 | { 110 | if (!disposedValue) 111 | { 112 | if (disposing) 113 | { 114 | Task t = Stop(); 115 | t.Wait(); 116 | } 117 | 118 | // TODO: アンマネージド リソース (アンマネージド オブジェクト) を解放し、ファイナライザーをオーバーライドします 119 | // TODO: 大きなフィールドを null に設定します 120 | disposedValue = true; 121 | } 122 | } 123 | 124 | // // TODO: 'Dispose(bool disposing)' にアンマネージド リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします 125 | // ~SoundRecorder() 126 | // { 127 | // // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します 128 | // Dispose(disposing: false); 129 | // } 130 | 131 | public void Dispose() 132 | { 133 | // このコードを変更しないでください。クリーンアップ コードを 'Dispose(bool disposing)' メソッドに記述します 134 | Dispose(disposing: true); 135 | GC.SuppressFinalize(this); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/Speech/Speech.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {7787A468-A1BB-4077-85BC-543258610060} 8 | Library 9 | Properties 10 | Speech 11 | Speech 12 | v4.8 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | AnyCPU 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | true 36 | bin\x86\Debug\ 37 | DEBUG;TRACE 38 | full 39 | AnyCPU 40 | prompt 41 | MinimumRecommendedRules.ruleset 42 | 43 | 44 | bin\x86\Release\ 45 | TRACE 46 | true 47 | pdbonly 48 | x86 49 | prompt 50 | MinimumRecommendedRules.ruleset 51 | 52 | 53 | 54 | ..\packages\Codeer.Friendly.2.6.1\lib\net40\Codeer.Friendly.dll 55 | 56 | 57 | ..\packages\Codeer.Friendly.2.6.1\lib\net40\Codeer.Friendly.Dynamic.dll 58 | 59 | 60 | ..\packages\Codeer.Friendly.Windows.2.15.0\lib\net20\Codeer.Friendly.Windows.dll 61 | 62 | 63 | ..\packages\Codeer.Friendly.Windows.Grasp.2.12.0\lib\net35\Codeer.Friendly.Windows.Grasp.2.0.dll 64 | 65 | 66 | ..\packages\Codeer.Friendly.Windows.Grasp.2.12.0\lib\net35\Codeer.Friendly.Windows.Grasp.3.5.dll 67 | 68 | 69 | ..\packages\Codeer.TestAssistant.GeneratorToolKit.3.10.0\lib\net20\Codeer.TestAssistant.GeneratorToolKit.dll 70 | 71 | 72 | ..\packages\NAudio.1.8.4\lib\net35\NAudio.dll 73 | 74 | 75 | 76 | 77 | ..\packages\RM.Friendly.WPFStandardControls.1.46.1\lib\net40\RM.Friendly.WPFStandardControls.3.0.dll 78 | 79 | 80 | ..\packages\RM.Friendly.WPFStandardControls.1.46.1\lib\net40\RM.Friendly.WPFStandardControls.3.0.Generator.dll 81 | 82 | 83 | ..\packages\RM.Friendly.WPFStandardControls.1.46.1\lib\net40\RM.Friendly.WPFStandardControls.3.5.dll 84 | 85 | 86 | ..\packages\RM.Friendly.WPFStandardControls.1.46.1\lib\net40\RM.Friendly.WPFStandardControls.4.0.dll 87 | 88 | 89 | ..\packages\RM.Friendly.WPFStandardControls.1.46.1\lib\net40\RM.Friendly.WPFStandardControls.4.0.Generator.dll 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | {C866CA3A-32F7-11D2-9602-00C04F8EE628} 155 | 5 156 | 4 157 | 0 158 | tlbimp 159 | False 160 | True 161 | 162 | 163 | 164 | 171 | -------------------------------------------------------------------------------- /src/Speech/SpeechController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Speech 10 | { 11 | public class SpeechController 12 | { 13 | string[] enumerators = 14 | { 15 | "AIVOICEEnumerator", 16 | "AITalk3Enumerator", 17 | "VoiceroidPlusEnumerator", 18 | "Voiceroid2Enumerator", 19 | "Voiceroid64Enumerator", 20 | "GynoidTalkEnumerator", 21 | "OtomachiUnaTalkEnumerator", 22 | "CeVIOEnumerator", 23 | "CeVIO64Enumerator", 24 | "CeVIOAIEnumerator", 25 | "SAPI5Enumerator", 26 | "VOICEVOXEnumerator", 27 | "COEIROINKEnumerator", 28 | "SHAREVOXEnumerator", 29 | "VOICEPEAKEnumerator" 30 | }; 31 | ISpeechEnumerator[] speechEnumerator; 32 | 33 | private static SpeechController instance = null; 34 | BlockingCollection bc = new BlockingCollection(); 35 | 36 | private ISpeechEnumerator CreateInstance(string typeName) 37 | { 38 | Type type = Type.GetType("Speech." + typeName + ",Speech"); // Speechはアセンブリ名 39 | var instance = Activator.CreateInstance(type) as ISpeechEnumerator; 40 | return instance; 41 | } 42 | 43 | private SpeechController() 44 | { 45 | Parallel.ForEach(enumerators, e => 46 | { 47 | ISpeechEnumerator instance = CreateInstance(e); 48 | if(instance != null) 49 | { 50 | bc.Add(instance); 51 | } 52 | }); 53 | bc.CompleteAdding(); 54 | speechEnumerator = bc.ToArray(); 55 | bc.Dispose(); 56 | } 57 | 58 | public static SpeechEngineInfo[] GetAllSpeechEngine() 59 | { 60 | if(instance == null) 61 | { 62 | instance = new SpeechController(); 63 | } 64 | List info = new List(); 65 | 66 | foreach(var se in instance.speechEnumerator) 67 | { 68 | var e = se.GetSpeechEngineInfo(); 69 | if(e.Length > 0 && e[0].Is64BitProcess == Environment.Is64BitProcess) 70 | { 71 | info.AddRange(se.GetSpeechEngineInfo()); 72 | } 73 | } 74 | return info.ToArray(); 75 | } 76 | 77 | public static ISpeechController GetInstance(string libraryName) 78 | { 79 | var info = GetAllSpeechEngine(); 80 | foreach(var e in info) 81 | { 82 | if(e.LibraryName == libraryName && Environment.Is64BitProcess == e.Is64BitProcess) 83 | { 84 | return GetInstance(e); 85 | } 86 | } 87 | return null; 88 | } 89 | public static ISpeechController GetInstance(string libraryName, string engineName) 90 | { 91 | var info = GetAllSpeechEngine(); 92 | foreach (var e in info) 93 | { 94 | if (e.LibraryName == libraryName && e.EngineName == engineName && Environment.Is64BitProcess == e.Is64BitProcess) 95 | { 96 | return GetInstance(e); 97 | } 98 | } 99 | return null; 100 | } 101 | 102 | public static ISpeechController GetInstance(SpeechEngineInfo info) 103 | { 104 | if (instance == null) 105 | { 106 | instance = new SpeechController(); 107 | } 108 | foreach (var i in instance.speechEnumerator) 109 | { 110 | var controller = i.GetControllerInstance(info); 111 | if(controller != null) 112 | { 113 | return controller; 114 | } 115 | } 116 | return null; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Speech/SpeechEngineInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Speech 8 | { 9 | public class SpeechEngineInfo 10 | { 11 | /// 12 | /// 音声合成エンジンの名称 13 | /// 14 | public string EngineName { get; internal set; } 15 | /// 16 | /// 音声合成ライブラリの名称 17 | /// 18 | public string LibraryName { get; internal set; } 19 | /// 20 | /// 音声合成エンジンのパス(SAPIの場合は空文字) 21 | /// 22 | public string EnginePath { get; internal set; } 23 | /// 24 | /// 音声合成エンジンが64bitプロセスの場合はtrue 25 | /// 26 | public bool Is64BitProcess { get; internal set; } = false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Speech/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Speech/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/SpeechSample/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/SpeechSample/Options.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SpeechSample 9 | { 10 | class Options 11 | { 12 | [Option('t', "text", Required = false, HelpText = "発話するテキスト")] 13 | public string Text { get; set; } 14 | [Option('n', "Name", Required = false, HelpText = "音声合成エンジン名")] 15 | public string Name { get; set; } 16 | [Option('s', "speaker", Required = false, HelpText = "再生するスピーカー")] 17 | public string Speaker { get; set; } 18 | [Option('o', "output", Required = false, HelpText = "出力ファイル名(.wav)")] 19 | public string Output { get; set; } 20 | [Option('v', "verbose", Required = false, HelpText = "音声合成エンジン、スピーカーの列挙")] 21 | public bool Verbose { get; set; } = false; 22 | [Option('w', "whisper", Required = false, HelpText = "ささやき声で出力")] 23 | public bool Whisper { get; set; } = false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SpeechSample/Program.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using AudioSwitcher.AudioApi.CoreAudio; 3 | using Speech; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Net; 10 | using System.Security.Cryptography; 11 | using Speech.Effect; 12 | using System.Threading; 13 | 14 | namespace SpeechSample 15 | { 16 | class Program 17 | { 18 | static IEnumerable devices; 19 | 20 | static string name; 21 | static bool finished = false; 22 | [MTAThread] 23 | static void Main(string[] args) 24 | { 25 | try 26 | { 27 | Parser.Default.ParseArguments(args) 28 | .WithParsed(opt => 29 | { 30 | string[] voices = GetLibraryName(); 31 | DateTime now = DateTime.Now; 32 | string date = now.ToString("yyyy年 MM月 dd日"); 33 | string time = now.ToString("HH時 mm分 ss秒"); 34 | string text = time + "です。"; 35 | 36 | string name = voices[0]; 37 | string speaker = ""; 38 | string output = ""; 39 | 40 | bool interactiveMode = true; 41 | if (opt.Verbose) 42 | { 43 | ShowVerbose(); 44 | return; 45 | } 46 | if (opt.Text != null) 47 | { 48 | interactiveMode = false; 49 | text = opt.Text.Replace("{date}", date).Replace("{time}",time); 50 | } 51 | if (opt.Name != null) 52 | { 53 | interactiveMode = false; 54 | name = opt.Name; 55 | } 56 | if (opt.Speaker != null) 57 | { 58 | devices = new CoreAudioController().GetPlaybackDevices(); 59 | speaker = opt.Speaker; 60 | ChangeSpeaker(speaker); 61 | } 62 | if (opt.Output != null) 63 | { 64 | interactiveMode = false; 65 | output = opt.Output; 66 | } 67 | if (interactiveMode) 68 | { 69 | InteractiveMode(); 70 | return; 71 | } 72 | if (output == "") 73 | { 74 | if (opt.Whisper) 75 | { 76 | WhisperMode(name, text); 77 | } 78 | else 79 | { 80 | OneShotPlayMode(name, text); 81 | } 82 | } 83 | else 84 | { 85 | RecordMode(name, text, output); 86 | } 87 | while (!finished) 88 | { 89 | Task.Delay(100); 90 | } 91 | }); 92 | }catch (Exception ex) 93 | { 94 | Console.WriteLine(ex.Message); 95 | return; 96 | } 97 | } 98 | 99 | private static string[] GetLibraryName() 100 | { 101 | var engines = SpeechController.GetAllSpeechEngine(); 102 | var names = from c in engines 103 | select c.LibraryName; 104 | return names.ToArray(); 105 | } 106 | 107 | private static void OneShotPlayMode(string libraryName, string text) 108 | { 109 | 110 | var engines = SpeechController.GetAllSpeechEngine(); 111 | var engine = SpeechController.GetInstance(libraryName); 112 | if (engine == null) 113 | { 114 | Console.WriteLine($"{libraryName} を起動できませんでした。"); 115 | return; 116 | } 117 | engine.Activate(); 118 | engine.Finished += (s, a) => 119 | { 120 | finished = true; 121 | engine.Dispose(); 122 | }; 123 | engine.Play(text); 124 | 125 | } 126 | private static void WhisperMode(string libraryName, string text) 127 | { 128 | string tempFile = "normal.wav"; 129 | string whisperFile = "whisper.wav"; 130 | 131 | var engines = SpeechController.GetAllSpeechEngine(); 132 | var engine = SpeechController.GetInstance(libraryName); 133 | if (engine == null) 134 | { 135 | Console.WriteLine($"{libraryName} を起動できませんでした。"); 136 | return; 137 | } 138 | engine.Activate(); 139 | 140 | SoundRecorder recorder = new SoundRecorder(tempFile); 141 | { 142 | recorder.PostWait = 300; 143 | 144 | engine.Finished += (s, a) => 145 | { 146 | finished = true; 147 | }; 148 | 149 | recorder.Start(); 150 | engine.Play(text); 151 | } 152 | 153 | while (!finished) 154 | { 155 | Thread.Sleep(100); 156 | } 157 | engine.Dispose(); 158 | Task t = recorder.Stop(); 159 | t.Wait(); 160 | // ささやき声に変換 161 | Whisper whisper = new Whisper(); 162 | Wave wave = new Wave(); 163 | wave.Read(tempFile); 164 | whisper.Convert(wave); 165 | wave.Write(whisperFile, wave.Data); 166 | 167 | //// 変換した音声を再生 168 | SoundPlayer sp = new SoundPlayer(); 169 | sp.Play(whisperFile); 170 | 171 | 172 | } 173 | public static void RecordMode(string libraryName, string text, string outputFilename) 174 | { 175 | SoundRecorder recorder = new SoundRecorder(outputFilename); 176 | recorder.PostWait = 300; 177 | 178 | var engines = SpeechController.GetAllSpeechEngine(); 179 | var engine = SpeechController.GetInstance(libraryName); 180 | if (engine == null) 181 | { 182 | Console.WriteLine($"{libraryName} を起動できませんでした。"); 183 | return; 184 | } 185 | 186 | engine.Activate(); 187 | engine.Finished += (s, a) => 188 | { 189 | Task t = recorder.Stop(); 190 | t.Wait(); 191 | finished = true; 192 | engine.Dispose(); 193 | }; 194 | recorder.Start(); 195 | engine.Play(text); 196 | } 197 | private static void InteractiveMode() 198 | { 199 | ShowVerbose(); 200 | 201 | // ライブラリ名を入力(c.LibraryName列) 202 | Console.Write("\r\nLibraryName> "); 203 | name = Console.ReadLine().Trim(); 204 | 205 | // 対象となるライブラリを実行 206 | var engine = SpeechController.GetInstance(name); 207 | if (engine == null) 208 | { 209 | Console.WriteLine($"{name} を起動できませんでした。"); 210 | Console.ReadKey(); 211 | return; 212 | } 213 | // 設定した音声の再生が終了したときに呼び出される処理を設定 214 | engine.Finished += Engine_Finished; 215 | 216 | // 音声合成エンジンを起動 217 | engine.Activate(); 218 | engine.SetVolume(1.0f); 219 | engine.SetPitch(1.0f); 220 | engine.SetSpeed(1.0f); 221 | engine.SetPitchRange(1.0f); 222 | string message = $"音声合成エンジン {engine.Info.EngineName}、{engine.Info.LibraryName}を起動しました。"; 223 | engine.Play(message); // 音声再生は非同期実行される 224 | Console.WriteLine(message); 225 | 226 | string line = ""; 227 | while(true) 228 | { 229 | line = Console.ReadLine(); 230 | if (line.Trim() == "") 231 | { 232 | engine.Dispose(); 233 | return; 234 | } 235 | try 236 | { 237 | engine.Stop(); // 喋っている途中に文字が入力されたら再生をストップ 238 | engine.Play(line); // 音声再生は非同期実行される 239 | Console.WriteLine($"Volume: {engine.GetVolume()}, Speed: {engine.GetSpeed()}, Pitch: {engine.GetPitch()}, PitchRange: {engine.GetPitchRange()}"); 240 | }catch(Exception ex) 241 | { 242 | Console.WriteLine(ex.Message); 243 | } 244 | } 245 | } 246 | 247 | private static void ChangeSpeaker(string name) 248 | { 249 | var speakers = (from c in devices 250 | where c.FullName.IndexOf(name) >= 0 251 | select c).ToArray(); 252 | if (speakers.Length > 0) 253 | { 254 | speakers[0].SetAsDefault(); 255 | } 256 | else 257 | { 258 | Console.WriteLine("Speaker not found."); 259 | } 260 | } 261 | private static void Engine_Finished(object sender, EventArgs e) 262 | { 263 | Console.WriteLine("* 再生完了 *"); 264 | Console.Write($"{name}> "); 265 | } 266 | 267 | private static void ShowVerbose() 268 | { 269 | // インストール済み音声合成ライブラリの列挙 270 | var names = GetLibraryName(); 271 | Console.WriteLine("インストール済み音声合成ライブラリ"); 272 | string bit = Environment.Is64BitProcess ? "64 bit" : "32 bit"; 273 | Console.WriteLine($"※ このアプリケーションは {bit}プロセスのため、{bit}のライブラリのみが列挙されます。"); 274 | Console.WriteLine("-----"); 275 | foreach (var s in names) 276 | { 277 | Console.WriteLine(s); 278 | } 279 | Console.WriteLine("-----"); 280 | 281 | // 接続先スピーカーの列挙 282 | Console.WriteLine("接続先スピーカー"); 283 | Console.WriteLine("-----"); 284 | devices = new CoreAudioController().GetPlaybackDevices(); 285 | string speaker = (devices.ToArray())[0].FullName; 286 | foreach (var d in devices) 287 | { 288 | Console.WriteLine($"{d.FullName}"); 289 | } 290 | Console.WriteLine("-----"); 291 | } 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/SpeechSample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 6 | // アセンブリに関連付けられている情報を変更するには、 7 | // これらの属性値を変更してください。 8 | [assembly: AssemblyTitle("SpeechSample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SpeechSample")] 13 | [assembly: AssemblyCopyright("Copyright © 2020 ksasao")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから 18 | // 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、 19 | // その型の ComVisible 属性を true に設定してください。 20 | [assembly: ComVisible(false)] 21 | 22 | // このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります 23 | [assembly: Guid("bb36cbbb-d522-409b-a6bf-7ac39c77a9ff")] 24 | 25 | // アセンブリのバージョン情報は次の 4 つの値で構成されています: 26 | // 27 | // メジャー バージョン 28 | // マイナー バージョン 29 | // ビルド番号 30 | // Revision 31 | // 32 | // すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を 33 | // 既定値にすることができます: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/SpeechSample/SpeechSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF} 8 | Exe 9 | Properties 10 | SpeechSample 11 | SpeechSample 12 | v4.8 13 | 512 14 | true 15 | 16 | 17 | 18 | x86 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | false 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | {7787a468-a1bb-4077-85bc-543258610060} 58 | Speech 59 | 60 | 61 | 62 | 63 | 3.0.0.1 64 | 65 | 66 | 2.8.0 67 | 68 | 69 | 70 | 77 | -------------------------------------------------------------------------------- /src/SpeechWebServer/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/SpeechWebServer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 6 | // アセンブリに関連付けられている情報を変更するには、 7 | // これらの属性値を変更してください。 8 | [assembly: AssemblyTitle("SpeechWebServer")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SpeechWebServer")] 13 | [assembly: AssemblyCopyright("Copyright © 2020 ksasao")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから 18 | // 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、 19 | // その型の ComVisible 属性を true に設定してください。 20 | [assembly: ComVisible(false)] 21 | 22 | // このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります 23 | [assembly: Guid("bb36cbbb-d522-409b-a6bf-7ac39c77a9ff")] 24 | 25 | // アセンブリのバージョン情報は次の 4 つの値で構成されています: 26 | // 27 | // メジャー バージョン 28 | // マイナー バージョン 29 | // ビルド番号 30 | // Revision 31 | // 32 | // すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を 33 | // 既定値にすることができます: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.0.4.0")] 36 | [assembly: AssemblyFileVersion("0.0.4.0")] 37 | -------------------------------------------------------------------------------- /src/SpeechWebServer/SpeechWebServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {471C9269-34CC-45BC-996E-2610DA84C4C8} 8 | Exe 9 | Properties 10 | SpeechWebServer 11 | SpeechWebServer 12 | v4.8 13 | 512 14 | true 15 | 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | true 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | false 37 | 38 | 39 | app.manifest 40 | 41 | 42 | true 43 | bin\x64\Debug\ 44 | DEBUG;TRACE 45 | full 46 | AnyCPU 47 | 7.3 48 | prompt 49 | MinimumRecommendedRules.ruleset 50 | false 51 | 52 | 53 | bin\x64\Release\ 54 | TRACE 55 | true 56 | pdbonly 57 | x64 58 | 7.3 59 | prompt 60 | MinimumRecommendedRules.ruleset 61 | true 62 | 63 | 64 | 65 | ..\packages\AudioSwitcher.AudioApi.3.0.0\lib\net40\AudioSwitcher.AudioApi.dll 66 | 67 | 68 | ..\packages\AudioSwitcher.AudioApi.CoreAudio.3.0.0.1\lib\net40\AudioSwitcher.AudioApi.CoreAudio.dll 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | {7787a468-a1bb-4077-85bc-543258610060} 92 | Speech 93 | 94 | 95 | 96 | 97 | PreserveNewest 98 | 99 | 100 | 101 | 108 | -------------------------------------------------------------------------------- /src/SpeechWebServer/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 59 | 60 | 61 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/SpeechWebServer/html/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | GitHubに移動します 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/SpeechWebServer/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/TTSController.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30611.23 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpeechSample", "SpeechSample\SpeechSample.csproj", "{BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speech", "Speech\Speech.csproj", "{7787A468-A1BB-4077-85BC-543258610060}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpeechWebServer", "SpeechWebServer\SpeechWebServer.csproj", "{471C9269-34CC-45BC-996E-2610DA84C4C8}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|x64 = Debug|x64 16 | Debug|x86 = Debug|x86 17 | Release|Any CPU = Release|Any CPU 18 | Release|x64 = Release|x64 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Debug|x64.ActiveCfg = Debug|Any CPU 25 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Debug|x64.Build.0 = Debug|Any CPU 26 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Debug|x86.ActiveCfg = Debug|Any CPU 27 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Debug|x86.Build.0 = Debug|Any CPU 28 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Release|x64.ActiveCfg = Release|Any CPU 31 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Release|x64.Build.0 = Release|Any CPU 32 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Release|x86.ActiveCfg = Release|Any CPU 33 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Release|x86.Build.0 = Release|Any CPU 34 | {7787A468-A1BB-4077-85BC-543258610060}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {7787A468-A1BB-4077-85BC-543258610060}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {7787A468-A1BB-4077-85BC-543258610060}.Debug|x64.ActiveCfg = Debug|Any CPU 37 | {7787A468-A1BB-4077-85BC-543258610060}.Debug|x64.Build.0 = Debug|Any CPU 38 | {7787A468-A1BB-4077-85BC-543258610060}.Debug|x86.ActiveCfg = Debug|x86 39 | {7787A468-A1BB-4077-85BC-543258610060}.Debug|x86.Build.0 = Debug|x86 40 | {7787A468-A1BB-4077-85BC-543258610060}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {7787A468-A1BB-4077-85BC-543258610060}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {7787A468-A1BB-4077-85BC-543258610060}.Release|x64.ActiveCfg = Release|Any CPU 43 | {7787A468-A1BB-4077-85BC-543258610060}.Release|x64.Build.0 = Release|Any CPU 44 | {7787A468-A1BB-4077-85BC-543258610060}.Release|x86.ActiveCfg = Release|x86 45 | {7787A468-A1BB-4077-85BC-543258610060}.Release|x86.Build.0 = Release|x86 46 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Debug|x64.ActiveCfg = Debug|x64 49 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Debug|x64.Build.0 = Debug|x64 50 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Debug|x86.ActiveCfg = Debug|Any CPU 51 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Debug|x86.Build.0 = Debug|Any CPU 52 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Release|x64.ActiveCfg = Release|x64 55 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Release|x64.Build.0 = Release|x64 56 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Release|x86.ActiveCfg = Release|Any CPU 57 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Release|x86.Build.0 = Release|Any CPU 58 | EndGlobalSection 59 | GlobalSection(SolutionProperties) = preSolution 60 | HideSolutionNode = FALSE 61 | EndGlobalSection 62 | GlobalSection(ExtensibilityGlobals) = postSolution 63 | SolutionGuid = {3450397B-1546-4FA1-8B59-1653317CE540} 64 | EndGlobalSection 65 | EndGlobal 66 | -------------------------------------------------------------------------------- /src/TTSController_vs2019.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30611.23 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpeechSample", "SpeechSample\SpeechSample.csproj", "{BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speech", "Speech\Speech.csproj", "{7787A468-A1BB-4077-85BC-543258610060}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpeechWebServer", "SpeechWebServer\SpeechWebServer.csproj", "{471C9269-34CC-45BC-996E-2610DA84C4C8}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|x64 = Debug|x64 16 | Debug|x86 = Debug|x86 17 | Release|Any CPU = Release|Any CPU 18 | Release|x64 = Release|x64 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Debug|x64.ActiveCfg = Debug|Any CPU 25 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Debug|x64.Build.0 = Debug|Any CPU 26 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Debug|x86.ActiveCfg = Debug|Any CPU 27 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Debug|x86.Build.0 = Debug|Any CPU 28 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Release|x64.ActiveCfg = Release|Any CPU 31 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Release|x64.Build.0 = Release|Any CPU 32 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Release|x86.ActiveCfg = Release|Any CPU 33 | {BB36CBBB-D522-409B-A6BF-7AC39C77A9FF}.Release|x86.Build.0 = Release|Any CPU 34 | {7787A468-A1BB-4077-85BC-543258610060}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {7787A468-A1BB-4077-85BC-543258610060}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {7787A468-A1BB-4077-85BC-543258610060}.Debug|x64.ActiveCfg = Debug|Any CPU 37 | {7787A468-A1BB-4077-85BC-543258610060}.Debug|x64.Build.0 = Debug|Any CPU 38 | {7787A468-A1BB-4077-85BC-543258610060}.Debug|x86.ActiveCfg = Debug|x86 39 | {7787A468-A1BB-4077-85BC-543258610060}.Debug|x86.Build.0 = Debug|x86 40 | {7787A468-A1BB-4077-85BC-543258610060}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {7787A468-A1BB-4077-85BC-543258610060}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {7787A468-A1BB-4077-85BC-543258610060}.Release|x64.ActiveCfg = Release|Any CPU 43 | {7787A468-A1BB-4077-85BC-543258610060}.Release|x64.Build.0 = Release|Any CPU 44 | {7787A468-A1BB-4077-85BC-543258610060}.Release|x86.ActiveCfg = Release|x86 45 | {7787A468-A1BB-4077-85BC-543258610060}.Release|x86.Build.0 = Release|x86 46 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Debug|x64.ActiveCfg = Debug|x64 49 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Debug|x64.Build.0 = Debug|x64 50 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Debug|x86.ActiveCfg = Debug|Any CPU 51 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Debug|x86.Build.0 = Debug|Any CPU 52 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Release|x64.ActiveCfg = Release|x64 55 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Release|x64.Build.0 = Release|x64 56 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Release|x86.ActiveCfg = Release|Any CPU 57 | {471C9269-34CC-45BC-996E-2610DA84C4C8}.Release|x86.Build.0 = Release|Any CPU 58 | EndGlobalSection 59 | GlobalSection(SolutionProperties) = preSolution 60 | HideSolutionNode = FALSE 61 | EndGlobalSection 62 | GlobalSection(ExtensibilityGlobals) = postSolution 63 | SolutionGuid = {3450397B-1546-4FA1-8B59-1653317CE540} 64 | EndGlobalSection 65 | EndGlobal 66 | --------------------------------------------------------------------------------