using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Codeer.Friendly; using Codeer.Friendly.Windows; using Codeer.Friendly.Windows.Grasp; using Codeer.Friendly.Windows.NativeStandardControls; using Ong.Friendly.FormsStandardControls; using ScDriver; namespace ScDriver.VoiceroidEx { public class ScDeviceDriver : ScBaseDriver, IScBaseDriver { private readonly string DrvName = "VoiceroidEx.Driver@echoseika.hgotoh.jp"; private readonly string DrvVersion = "20200430/c"; private readonly string DrvProdName = "VOICEROID+EX"; private readonly int CidBase = 1700; private readonly string[] VoiceroidExTitles = { "VOICEROID+ 京町セイカ EX", "VOICEROID+ 民安ともえ EX", "VOICEROID+ 結月ゆかり EX", "VOICEROID+ 東北ずん子 EX", "VOICEROID+ 水奈瀬コウ EX", "VOICEROID+ 東北きりたん EX", "VOICEROID+ 琴葉茜", "VOICEROID+ 琴葉葵", "VOICEROID+ 東北ずん子", "VOICEROID+ 鷹の爪 吉田くん EX", "VOICEROID+ 月読アイ EX", "VOICEROID+ 月読ショウタ EX", "音街ウナTalk Ex", "ギャラ子Talk", "ギャラ子 Talk" }; public ScDeviceDriver() { ScDrvName = DrvName; ScDrvVersion = DrvVersion; ScDrvProdName = DrvProdName; CidBaseIndex = CidBase; AvatorParams = new Dictionary(); IsAlive = false; foreach (VoiceroidEx.AvatorParam avator in GetVoiceroidProcess()) { AvatorParams.Add(avator.AvatorIndex, avator); avator.AvatorUI = new VoiceroidEx.AvatorUIParam(); try { avator.AvatorUI._app = new WindowsAppFriend(avator.AvatorProcess); avator.AvatorUI.uiTreeTop = WindowControl.FromZTop(avator.AvatorUI._app); // Zインデクスはコーディア様の TestAssistantで確認可能。音声効果タブへ切り替えてUI要素を取得する。 dynamic VoiceroidExUiTab = new FormsTabControl(avator.AvatorUI.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0)); VoiceroidExUiTab.EmulateTabSelect(2); avator.AvatorUI.TalkTextBox = avator.AvatorUI.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 1); avator.AvatorUI.PlayButton = new FormsButton(avator.AvatorUI.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 0, 3)); avator.AvatorUI.SaveButton = new FormsButton(avator.AvatorUI.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 0, 1)); avator.AvatorUI.VolumeText = new FormsTextBox(avator.AvatorUI.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0, 0, 0, 8)); avator.AvatorUI.SpeedText = new FormsTextBox(avator.AvatorUI.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0, 0, 0, 9)); avator.AvatorUI.PitchText = new FormsTextBox(avator.AvatorUI.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0, 0, 0, 10)); avator.AvatorUI.IntonationText = new FormsTextBox(avator.AvatorUI.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0, 0, 0, 11)); } catch (Exception e) { ThrowException(string.Format(@"{0} {1}", e.Message, e.StackTrace)); } avator.VoiceEffects_default = new Dictionary { { EnumVoiceEffect.volume, new EffectValueInfo(GetSliderValue(avator.AvatorIndex, EnumVoiceEffect.volume), 0.0m, 2.0m, 0.01m)}, { EnumVoiceEffect.speed, new EffectValueInfo(GetSliderValue(avator.AvatorIndex, EnumVoiceEffect.speed), 0.5m, 4.0m, 0.01m)}, { EnumVoiceEffect.pitch, new EffectValueInfo(GetSliderValue(avator.AvatorIndex, EnumVoiceEffect.pitch), 0.5m, 2.0m, 0.01m)}, { EnumVoiceEffect.intonation, new EffectValueInfo(GetSliderValue(avator.AvatorIndex, EnumVoiceEffect.intonation), 0.0m, 2.0m, 0.01m)} }; avator.VoiceEffects = new Dictionary { { EnumVoiceEffect.volume, new EffectValueInfo(GetSliderValue(avator.AvatorIndex, EnumVoiceEffect.volume), 0.0m, 2.0m, 0.01m)}, { EnumVoiceEffect.speed, new EffectValueInfo(GetSliderValue(avator.AvatorIndex, EnumVoiceEffect.speed), 0.5m, 4.0m, 0.01m)}, { EnumVoiceEffect.pitch, new EffectValueInfo(GetSliderValue(avator.AvatorIndex, EnumVoiceEffect.pitch), 0.5m, 2.0m, 0.01m)}, { EnumVoiceEffect.intonation, new EffectValueInfo(GetSliderValue(avator.AvatorIndex, EnumVoiceEffect.intonation), 0.0m, 2.0m, 0.01m)} }; avator.VoiceEmotions_default = new Dictionary(); avator.VoiceEmotions = new Dictionary(); } IsAlive = AvatorParams.Count != 0; } private List GetVoiceroidProcess() { Process[] ProcessList = Process.GetProcesses(); List VoiceroidProcesses = new List(); //for (int idx = 0; idx < VoiceroidExTitles.Length; idx++) //{ // string WinTitle1 = VoiceroidExTitles[idx]; // string WinTitle2 = WinTitle1 + "*"; // foreach (Process p in ProcessList) // { // if ((p.MainWindowHandle != IntPtr.Zero) && // ((p.MainWindowTitle.Equals(WinTitle1)) || (p.MainWindowTitle.Equals(WinTitle2)))) // { // VoiceroidEx.AvatorParam avator = new VoiceroidEx.AvatorParam(); // avator.AvatorIndex = idx; // avator.AvatorName = VoiceroidExTitles[idx]; // avator.AvatorProcess = p; // VoiceroidProcesses.Add(avator); // break; // } // } //} int idx = 0; foreach(string prodTitle in VoiceroidExTitles) { string WinTitle1 = prodTitle; string WinTitle2 = WinTitle1 + "*"; foreach (Process p in ProcessList) { if ((p.MainWindowHandle != IntPtr.Zero) && ((p.MainWindowTitle.Equals(WinTitle1)) || (p.MainWindowTitle.Equals(WinTitle2)))) { VoiceroidEx.AvatorParam avator = new VoiceroidEx.AvatorParam(); avator.AvatorIndex = idx; avator.AvatorName = prodTitle; avator.AvatorProcess = p; VoiceroidProcesses.Add(avator); idx++; break; } } } return VoiceroidProcesses; } public void Dispose() { Dispose(true); } /// /// 指定話者で指定テキストで発声 /// /// 話者CID /// 発声させるテキスト /// 発声にかかった時間(ミリ秒) public override double Play(int cid, string talkText) { Stopwatch stopWatch = new Stopwatch(); int avatorIdx = ConvertAvatorIndex(cid); VoiceroidEx.AvatorParam avator = AvatorParams[avatorIdx] as VoiceroidEx.AvatorParam; avator.Semaphore.Wait(); if (avator.AvatorUI.PlayButton == null) return 0.0; if (avator.AvatorUI.SaveButton == null) return 0.0; if (avator.AvatorUI.TalkTextBox == null) return 0.0; dynamic VoiceroidExUiTab = new FormsTabControl(avator.AvatorUI.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0)); VoiceroidExUiTab.EmulateTabSelect(2); ApplyEffectParameters(avatorIdx); ApplyEmotionParameters(avatorIdx); // 再生中なので再生終了を待つ(音声保存ボタンがEnableになるのを待つ) if (!avator.AvatorUI.SaveButton.Enabled) { while (!avator.AvatorUI.SaveButton.Enabled) { Thread.Sleep(10); } } avator.AvatorUI.TalkTextBox["Text"](talkText); Thread.Sleep(10); stopWatch.Start(); avator.AvatorUI.PlayButton.EmulateClick(); // 再生開始を待つ(音声保存ボタンがDisableになるのを待つ) if (avator.AvatorUI.SaveButton.Enabled) { while (avator.AvatorUI.SaveButton.Enabled) { Thread.Sleep(10); } } // 再生終了を待つ(音声保存ボタンがEnableになるのを待つ) if (!avator.AvatorUI.SaveButton.Enabled) { while (!avator.AvatorUI.SaveButton.Enabled) { Thread.Sleep(10); } } stopWatch.Stop(); avator.Semaphore.Release(); return stopWatch.ElapsedMilliseconds; } /// /// 指定話者で指定テキストで発声 /// /// 話者CID /// 発声させるテキスト public override void PlayAsync(int cid, string talkText) { int avatorIdx = ConvertAvatorIndex(cid); VoiceroidEx.AvatorParam avator = AvatorParams[avatorIdx] as VoiceroidEx.AvatorParam; Task.Run(() => { avator.Semaphore.Wait(); if (avator.AvatorUI.PlayButton == null) return; if (avator.AvatorUI.SaveButton == null) return; if (avator.AvatorUI.TalkTextBox == null) return; dynamic VoiceroidExUiTab = new FormsTabControl(avator.AvatorUI.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0)); VoiceroidExUiTab.EmulateTabSelect(2); ApplyEffectParameters(avatorIdx); ApplyEmotionParameters(avatorIdx); // 再生中なので再生終了を待つ(音声保存ボタンがEnableになるのを待つ) if (!avator.AvatorUI.SaveButton.Enabled) { while (!avator.AvatorUI.SaveButton.Enabled) { Thread.Sleep(10); } } avator.AvatorUI.TalkTextBox["Text"](talkText); Thread.Sleep(10); avator.AvatorUI.PlayButton.EmulateClick(); // 再生開始を待つ(音声保存ボタンがDisableになるのを待つ) if (avator.AvatorUI.SaveButton.Enabled) { while (avator.AvatorUI.SaveButton.Enabled) { Thread.Sleep(10); } } // 再生終了を待つ(音声保存ボタンがEnableになるのを待つ) if (!avator.AvatorUI.SaveButton.Enabled) { while (!avator.AvatorUI.SaveButton.Enabled) { Thread.Sleep(10); } } avator.Semaphore.Release(); }); } /// /// 指定話者で指定テキストで発声した結果をファイルに保存 /// /// 話者CID /// 発声させるテキスト /// 保存先ファイル名 /// 0.0ミリ秒固定 public override double Save(int cid, string talkText, string saveFilename) { int avatorIdx = ConvertAvatorIndex(cid); VoiceroidEx.AvatorParam avator = AvatorParams[avatorIdx] as VoiceroidEx.AvatorParam; if (avator.AvatorUI.PlayButton == null) return 0.0; if (avator.AvatorUI.SaveButton == null) return 0.0; if (avator.AvatorUI.TalkTextBox == null) return 0.0; dynamic VoiceroidExUiTab = new FormsTabControl(avator.AvatorUI.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0)); VoiceroidExUiTab.EmulateTabSelect(2); ApplyEffectParameters(avatorIdx); ApplyEmotionParameters(avatorIdx); if (!avator.AvatorUI.SaveButton.Enabled) { while (!avator.AvatorUI.SaveButton.Enabled) { Thread.Sleep(10); } } avator.AvatorUI.TalkTextBox["Text"](talkText); Thread.Sleep(10); avator.AvatorUI.SaveButton.EmulateClick(new Async()); bool finish_savefileSetup = false; while (finish_savefileSetup == false) { //名前を付けて保存 ダイアログで名前を設定 var FileDlgs = WindowControl.GetFromWindowText(avator.AvatorUI._app, "音声ファイルの保存"); try { if ((FileDlgs.Length != 0) && (FileDlgs[0].WindowClassName == "#32770")) { // https://github.com/mikoto2000/TTSController UI特定の記述を参照 NativeButton OkButton = new NativeButton(FileDlgs[0].IdentifyFromDialogId(1)); NativeEdit SaveNameText = new NativeEdit(FileDlgs[0].IdentifyFromZIndex(11, 0, 4, 0, 0)); //ファイル名を設定 SaveNameText.EmulateChangeText(saveFilename); Thread.Sleep(100); //OKボタンを押す OkButton.EmulateClick(new Async()); finish_savefileSetup = true; } } catch (Exception) { // } Thread.Sleep(10); } return 0.0; } /// /// 感情パラメタをデフォルト値に戻す /// /// 話者CID public override void ResetVoiceEmotion(int cid) { int avatorIdx = ConvertAvatorIndex(cid); AvatorParam avator = AvatorParams[avatorIdx] as AvatorParam; foreach (var emotion in avator.VoiceEmotions_default) { avator.VoiceEmotions[emotion.Key].value = emotion.Value.value; } ApplyEmotionParameters(avatorIdx); } /// /// 音声効果をデフォルト値に戻す /// /// 話者CID public override void ResetVoiceEffect(int cid) { int avatorIdx = ConvertAvatorIndex(cid); AvatorParam avator = AvatorParams[avatorIdx] as AvatorParam; foreach (var effect in avator.VoiceEffects_default) { avator.VoiceEffects[effect.Key].value = effect.Value.value; } ApplyEffectParameters(avatorIdx); } public override void Dispose(bool disposing) { if (Disposed) return; if (disposing) { foreach(var item in AvatorParams) { ResetVoiceEffect(item.Key + CidBase); ResetVoiceEmotion(item.Key + CidBase); (item.Value as VoiceroidEx.AvatorParam).AvatorUI._app.Dispose(); } AvatorParams.Clear(); //throw new NotImplementedException(); } Disposed = true; } private void ApplyEmotionParameters(int avatorIndex) { // } private void ApplyEffectParameters(int avatorIdx) { FormsTextBox TargetTextBox = null; double value = 0.00; VoiceroidEx.AvatorParam avator = AvatorParams[avatorIdx] as VoiceroidEx.AvatorParam; foreach (var effect in avator.VoiceEffects) { switch (effect.Key) { case EnumVoiceEffect.volume: TargetTextBox = avator.AvatorUI.VolumeText; break; case EnumVoiceEffect.speed: TargetTextBox = avator.AvatorUI.SpeedText; break; case EnumVoiceEffect.pitch: TargetTextBox = avator.AvatorUI.PitchText; break; case EnumVoiceEffect.intonation: TargetTextBox = avator.AvatorUI.IntonationText; break; } value = Convert.ToDouble(avator.VoiceEffects[effect.Key].value); if (TargetTextBox != null) { TargetTextBox.EmulateChangeText(string.Format("{0:0.00}", value)); } } } private decimal GetSliderValue(int avatorIdx, EnumVoiceEffect effect) { decimal value = 0.00m; FormsTextBox TargetTextBox = null; VoiceroidEx.AvatorParam avator = AvatorParams[avatorIdx] as VoiceroidEx.AvatorParam; switch (effect) { case EnumVoiceEffect.volume: TargetTextBox = avator.AvatorUI.VolumeText; break; case EnumVoiceEffect.speed: TargetTextBox = avator.AvatorUI.SpeedText; break; case EnumVoiceEffect.pitch: TargetTextBox = avator.AvatorUI.PitchText; break; case EnumVoiceEffect.intonation: TargetTextBox = avator.AvatorUI.IntonationText; break; } if (TargetTextBox != null) { value = Convert.ToDecimal(TargetTextBox.Text); } return value; } } }