大変申し訳ありませんがソースの解説をすることはしません。
2019/03/27
2018/06/09
VOICEROID2/+EX/CeVIOをコマンドラインからしゃべらせるのSeikaCenterで使っているソースコードで、VOICEROID操作関係を表示しています。
seikasayコマンドとSeikaCenterのいくつかのクラスだけ表示します。
VOICEROID2/+EX/CeVIOをコマンドラインからしゃべらせるの版と同期が取れているわけではないのでご注意ください。
SeikaCenterAPI.dll 経由でSeikaCenterにデータを渡します。コードの大半はコマンドラインのオプション解析処理です。
SeikaCenterAPI.dllの使用例でもあります。
プロパティ SeikaCenterControl.AvatorList と メソッド SeikaCenterControl.GetAvatorParams2() は動的にUIを作るときなんかに使えるかもしれませんねぇ。
using System; using System.Text; using SeikaCenter; using System.Threading; using System.IO; using seikasay; namespace SeikaSay { class Program { static string Version = "20190327/u cli"; static string DLLVersion; static string ffmt = @"{0}\{1}{2:000000}_{3}"; static void Main(string[] args) { Opts opt = new Opts(args); double wt = 0.0; SeikaCenterControl scc = new SeikaCenterControl(); DLLVersion = scc.DLLVersion; if (opt.isActive) { try { if (opt.useAvatorList) { Console.WriteLine("cid speaker"); Console.WriteLine("----- ---------------------------"); foreach (var item in scc.AvatorList) { Console.WriteLine("{0,5:d} {1}", item.Key, item.Value); } Console.WriteLine("----- ---------------------------"); return; } if (opt.useParamList) { Console.WriteLine("cid:{0}",opt.cid); Console.WriteLine("---------------------------------"); foreach (var item1 in scc.GetAvatorParams2(opt.cid)) { foreach (var item2 in item1.Value) { Console.WriteLine("{0,-8} : {1,-14} = {2} [{3}~{4}, step {5}]", item1.Key, item2.Key, item2.Value["value"], item2.Value["min"], item2.Value["max"], item2.Value["step"]); } } Console.WriteLine("---------------------------------"); return; } if (opt.useCurrentParamList) { Console.WriteLine("cid:{0}", opt.cid); Console.WriteLine("---------------------------------"); foreach (var item1 in scc.GetAvatorParams_current2(opt.cid)) { foreach (var item2 in item1.Value) { Console.WriteLine("{0,-8} : {1,-14} = {2} [{3}~{4}, step {5}]", item1.Key, item2.Key, item2.Value["value"], item2.Value["min"], item2.Value["max"], item2.Value["step"]); } } Console.WriteLine("---------------------------------"); return; } if (!scc.AvatorList.ContainsKey(opt.cid)) throw new Exception(string.Format("cid {0} not found.", opt.cid)); if (opt.sw != 0) Thread.Sleep(1000 * opt.sw); if (opt.talkText != "") { wt = scc.Talk(opt.cid, opt.talkText, opt.saveFilename, opt.effects, opt.emotions); if (opt.dispWaveTime) Console.WriteLine("time:{0:0,000}ms", wt); } else if (opt.inputFilename != "") { StringBuilder sb1 = new StringBuilder(); StringBuilder sb2 = new StringBuilder(); StringBuilder sb3 = new StringBuilder(); int count = 1; using (StreamReader tr = new StreamReader(opt.inputFilename, Encoding.UTF8, false)) { while (!tr.EndOfStream) { sb1.Clear(); sb2.Clear(); sb3.Clear(); string x = tr.ReadLine(); Console.WriteLine(x); sb1.Append(x); if (opt.savePath != "") { sb2.AppendFormat(ffmt + ".wav", opt.savePath, opt.savePrefix, count, opt.cid); sb3.AppendFormat(ffmt + ".txt", opt.savePath, opt.savePrefix, count, opt.cid); count++; } wt = scc.Talk(opt.cid, sb1.ToString(), sb2.ToString(), opt.effects, opt.emotions); if (opt.savePath != "") { using (StreamWriter xx = new StreamWriter(sb3.ToString())) { xx.WriteLine(sb1.ToString()); xx.Close(); } } if (opt.dispWaveTime) Console.WriteLine("time:{0:0,000}ms", wt); } } } else if (opt.isStdin) { StringBuilder sb1 = new StringBuilder(); StringBuilder sb2 = new StringBuilder(); StringBuilder sb3 = new StringBuilder(); int count = 1; do { sb1.Clear(); sb2.Clear(); sb3.Clear(); sb1.Append(Console.ReadLine()); if (opt.savePath != "") { sb2.AppendFormat(ffmt + ".wav", opt.savePath, opt.savePrefix, count, opt.cid); sb3.AppendFormat(ffmt + ".txt", opt.savePath, opt.savePrefix, count, opt.cid); count++; } wt = scc.Talk(opt.cid, sb1.ToString(), sb2.ToString(), opt.effects, opt.emotions); if (opt.savePath != "") { using (StreamWriter xx = new StreamWriter(sb3.ToString())) { xx.WriteLine(sb1.ToString()); xx.Close(); } } if (opt.dispWaveTime) Console.WriteLine("time:{0:0,000}ms", wt); } while (Console.In.Peek() > 0); } } catch (Exception e) { Console.WriteLine("ccc:{0}", e.Message + e.StackTrace); } } else { help(); } } static void help() { Console.WriteLine("seikasay command Version:{0}, DLL Version:{1}", Version, DLLVersion); Console.WriteLine(""); Console.WriteLine(@" print informations: seikasay [-list | -cid ID -params | -cid ID -current] options: -list : print speaker list. -params : print default effect parameters. -current : print current effect parameters. call TTS system: Use argument mode: seikasay -cid ID [-save filename ] [ [option [option [option [.... [option ] ] ] ] ] ] -t TalkText Use standard input mode(shift jis encoding): seikasay -cid ID [-savepath path ] [ [option [option [option [.... [option ] ] ] ] ] ] -stdin Use file input mode(utf-8 encoding no BOM): seikasay -cid ID [-savepath path ] [ [option [option [option [.... [option ] ] ] ] ] ] -f TextFile Common Options: -cid ID : select speaker. -save FILE: save voice. FILE = wave file name -savepath PATH: save path. PATH = wave file save path SAPI5 speaker options: -volume VOL : sound volume. VOL = 0 ~ 100 default 100 -speed SPD : play rate. SPD = -10 ~ 10 default 0 VOICEROID2/VOICEROID+EX speaker options: -volume VOL : sound volume. VOL = 0.00 ~ 2.00 default 1.00 -speed SPD : play speed. SPD = 0.50 ~ 4.00 default 1.00 -pitch PCH : play pitch. PCH = 0.50 ~ 2.00 default 1.00 -intonation ITN : play intonation. ITN = 0.00 ~ 2.00 default 1.00 -emotion KEY VAL : add emotion. VAL = 0.00 ~ 1.00 default 0.00 Example: -emotion ""喜び"" 0.8 Use CeVIO speaker options: -volume VOL : sound volume. VOL = 0 ~ 100 default 50 -speed SPD : play speed. SPD = 0 ~ 100 default 50 -pitch PCH : play pitch. PCH = 0 ~ 100 default 50 -alpha ALP : play alpha. ALP = 0 ~ 100 default 50 -intonation ITN : play intonation. ITN = 0 ~ 100 default 50 -emotion KEY VAL : add emotion. VAL = 0 ~ 100 default 0 Example: -emotion ""喜び"" 100 "); } } }
音声保存で使用する出力デバイスのキャプチャーで使います。
using System; using System.Collections.Generic; using System.Text.RegularExpressions; using System.IO; using System.Threading; using NAudio.CoreAudioApi; using NAudio.Wave; using NAudio.Wave.SampleProviders; namespace SeikaCenter { class AudioCapture { WasapiLoopbackCapture capDev = null; // ループバックキャプチャオブジェクト BufferedWaveProvider buffWaveProvider = null; WaveFileWriter capWriter = null; Stream fstream = null; WasapiOut loopbackDev = null; string tempFilePath; string waveFilePath; bool isDispatchDefault = false; public decimal SkipAudioHeadTime { get; set; } public string CapDeviceName { get; set; } public bool EnableLoopback { get; set; } private void capDataAvailable(object sender, WaveInEventArgs e) { capWriter.Write(e.Buffer, 0, e.BytesRecorded); if (isDispatchDefault) buffWaveProvider.AddSamples(e.Buffer, 0, e.BytesRecorded); } private void RecordingStopped(object sender, StoppedEventArgs e) { try { if (capWriter != null) { capWriter.Dispose(); capWriter = null; } if (fstream != null) { //fstream.Flush(); fstream.Close(); fstream.Dispose(); fstream = null; } //capDev.Dispose(); //capDev = null; } catch(Exception eau3) { Console.WriteLine("eau3:{0}", eau3.Message); } } private void InitCaptureSettings() { ReleaseCaptureObjects(); capDev = new WasapiLoopbackCapture(GetMMDevice(CapDeviceName)); capDev.ShareMode = AudioClientShareMode.Shared; capDev.DataAvailable += capDataAvailable; capDev.RecordingStopped += RecordingStopped; if (EnableLoopback) { loopbackDev = new WasapiOut(); buffWaveProvider = new BufferedWaveProvider(capDev.WaveFormat); loopbackDev.Init(buffWaveProvider); } } private void ReleaseCaptureObjects() { if (capDev != null) { capDev.DataAvailable -= capDataAvailable; capDev.RecordingStopped -= RecordingStopped; capDev.Dispose(); capDev = null; } loopbackDev?.Dispose(); capWriter?.Dispose(); } public AudioCapture() { CapDeviceName = ""; EnableLoopback = false; InitCaptureSettings(); } public AudioCapture(string capDevicename, bool enableLoopback) { ResetCapture(capDevicename, enableLoopback); } public void ResetCapture(string capDevicename, bool enableLoopback) { CapDeviceName = capDevicename; EnableLoopback = enableLoopback; InitCaptureSettings(); } public bool StartCapture(string WavFilePath) { waveFilePath = WavFilePath; tempFilePath = Regex.Replace(waveFilePath, @"\.[Ww][Aa][Vv]$", ".temp.wav"); try { if (File.Exists(tempFilePath)) File.Delete(tempFilePath); if (File.Exists(waveFilePath)) File.Delete(waveFilePath); fstream = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write, FileShare.Write); capWriter = new WaveFileWriter(fstream, capDev.WaveFormat); capDev.StartRecording(); loopbackDev?.Play(); } catch (Exception eff) { Console.WriteLine("eff:{0},{1}", eff.Message, eff.StackTrace); capWriter?.Dispose(); fstream?.Dispose(); return false; } return true; } public double GetWaveTime() { TimeSpan wavetime = new TimeSpan(0, 0, 0); using (var inputReader = new AudioFileReader(waveFilePath)) { var mono = new StereoToMonoSampleProvider(inputReader); mono.LeftVolume = 0.0f; // discard the left channel mono.RightVolume = 1.0f; // keep the right channel WaveFileWriter.CreateWaveFile16(waveFilePath, mono); wavetime = inputReader.TotalTime; } return wavetime.TotalMilliseconds == 0 ? -1.0 : wavetime.TotalMilliseconds; } public double StopCapture() { TimeSpan wavetime = new TimeSpan(0, 0, 0); try { loopbackDev?.Stop(); capDev.StopRecording(); while (capDev.CaptureState != CaptureState.Stopped) Thread.Sleep(5); int cnt1 = 0; while (IsLocked(tempFilePath)) { Thread.Sleep(10); cnt1++; if (cnt1 > 3000) return -1; } TimeSpan ms = TimeSpan.FromMilliseconds(Convert.ToDouble(SkipAudioHeadTime)); using (var inputReader = new AudioFileReader(tempFilePath)) { Console.WriteLine("time1:{0}", inputReader.CurrentTime); inputReader.CurrentTime = ms; Console.WriteLine("time2:{0}", inputReader.CurrentTime); var mono = new StereoToMonoSampleProvider(inputReader); mono.LeftVolume = 0.0f; // discard the left channel mono.RightVolume = 1.0f; // keep the right channel WaveFileWriter.CreateWaveFile16(waveFilePath, mono); wavetime = inputReader.TotalTime - ms; Console.WriteLine("time3:{0}", wavetime); } int cnt2 = 0; while (IsLocked(waveFilePath)) { Thread.Sleep(10); cnt2++; if (cnt2 > 3000) return -1; } if (File.Exists(tempFilePath)) File.Delete(tempFilePath); } catch(Exception eu2) { Console.WriteLine("eu2:{0}", eu2); } return wavetime.TotalMilliseconds == 0 ? -1.0: wavetime.TotalMilliseconds; } public bool PlayWaveFile(string WavFilePath) { try { var dev = GetMMDevice(CapDeviceName); using (var audioFile = new AudioFileReader(WavFilePath)) using (var outputDevice = new WasapiOut(dev, AudioClientShareMode.Shared, false, 0)) { outputDevice.Init(audioFile); outputDevice.Play(); while (outputDevice.PlaybackState == PlaybackState.Playing) { Thread.Sleep(5); } } dev?.Dispose(); } catch (Exception f2sd) { Console.WriteLine("f2sd:{0}", f2sd.Message); } return true; } public string GetDefaultMMDeviceName() { MMDevice mmCapDev = new MMDeviceEnumerator().GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); return mmCapDev.FriendlyName; } public string[] GetMMDeviceNames(string capDeviceName) { List<string> list = new List<string>(); foreach (var item in new MMDeviceEnumerator().EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active)) { list.Add(item.FriendlyName); } return list.ToArray(); } public MMDevice GetMMDevice(string mmDeviceName) { MMDevice mmCapDev = null; if (mmDeviceName == "") { mmCapDev = new MMDeviceEnumerator().GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); } else { foreach (var item in new MMDeviceEnumerator().EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active)) { if (item.FriendlyName == mmDeviceName) { mmCapDev = item; break; } } if (mmCapDev == null) { mmCapDev = new MMDeviceEnumerator().GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); } } return mmCapDev; } private bool IsLocked(string filePath) { FileStream stream = null; try { stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None); } catch { return true; } finally { if (stream != null) { stream.Close(); } } return false; } } }
クラス Voiceroid2 や gynoid のベースクラスになります。
using System; using System.Collections.Generic; using System.Threading; using System.Diagnostics; using Codeer.Friendly; using Codeer.Friendly.Windows; using Codeer.Friendly.Dynamic; using Codeer.Friendly.Windows.Grasp; using RM.Friendly.WPFStandardControls; using Codeer.Friendly.Windows.NativeStandardControls; namespace SeikaCenter { class AITalkEditor2Base : ProductControlBase { protected WindowsAppFriend _app = null; protected WindowControl uiTreeTop = null; protected WPFListView avatorListView_std = null; protected WPFListView avatorListView_usr = null; protected WPFTabControl voicePresetTab = null; protected WPFTabControl tuneTab = null; protected WPFTextBox talkTextBox = null; protected WPFButtonBase playButton = null; protected WPFButtonBase saveButton = null; protected Dictionary<int, avatorUIParam> avatorUIParams; protected class avatorUIParam { public int presetTabIndex = 0; public bool withEmotionParams = false; public WPFSlider volumeSlider = null; public WPFSlider speedSlider = null; public WPFSlider pitchSlider = null; public WPFSlider intonationSlider = null; public Dictionary<int, WPFSlider> emotionSliders = null; public Dictionary<string, int> emotionSliderIndexs = null; } protected override void Dispose(bool disposing) { if (this.disposed) return; if(disposing) { //_app?.Dispose(); } this.disposed = true; base.Dispose(disposing); } public AITalkEditor2Base() { // } public override bool SelectAvator(int avatorIndex) { if ((avatorIndex < 0) || (avatorIndex > avatorParams.Count)) return false; selectedAvator = avatorIndex; switch (avatorUIParams[avatorIndex].presetTabIndex) { case 0: default: if (avatorListView_std == null) return false; voicePresetTab.EmulateChangeSelectedIndex(0); avatorListView_std.EmulateChangeSelectedIndex(avatorParams[avatorIndex].avatorIndex); break; case 1: if (avatorListView_usr == null) return false; voicePresetTab.EmulateChangeSelectedIndex(1); avatorListView_usr.EmulateChangeSelectedIndex(avatorParams[avatorIndex].avatorIndex); break; } return true; } public override double Play() { Stopwatch sw = new Stopwatch(); bool f = false; if (playButton == null) return 0.0; if (saveButton == null) return 0.0; if (talkTextBox == null) return 0.0; tuneTab.EmulateChangeSelectedIndex(1); // 音声保存ボタンを使った再生終了判定を止めて、再生ボタンのアイコンの状態で判定する方法に変更する。 var items01 = playButton.LogicalTree(TreeRunDirection.Descendants).ByType("System.Windows.Controls.Image"); dynamic playButtonImage1 = items01[0].Dynamic(); // 再生アイコン。このアイコンが有効時?の判定はまだないな... dynamic playButtonImage2 = items01[1].Dynamic(); // 停止アイコン。処理ではこのアイコンのプロパティを持ている ApplyEffectParameters(); ApplyEmotionParameters(); // 再生中なので再生終了を待つ // ※再生ボタンのアイコンが再生アイコンに切り替わるのを待つ f = playButtonImage2.IsVisible; if (f) { Console.WriteLine("a:wait.."); while (f) { Thread.Sleep(10); f = playButtonImage2.IsVisible; } Console.WriteLine("a:finish"); } talkTextBox.EmulateChangeText(TalkText); Thread.Sleep(10); sw.Start(); playButton.EmulateClick(); // 再生開始を待つ // 再生ボタンのアイコンが停止アイコンに切り替わるのを待つ f = playButtonImage2.IsVisible; if (!f) { Console.WriteLine("b:wait.."); while (!f) { Thread.Sleep(10); f = playButtonImage2.IsVisible; } Console.WriteLine("b:finish"); } // 再生終了を待つ // 再生ボタンのアイコンが再生アイコンに切り替わるのを待つ f = playButtonImage2.IsVisible; if (f) { Console.WriteLine("c:wait.."); while (f) { Thread.Sleep(10); f = playButtonImage2.IsVisible; } Console.WriteLine("c:finish"); } sw.Stop(); return sw.ElapsedMilliseconds; } public override double SaveFromProduct(string saveFilename) { if (playButton == null) return 0.0; if (saveButton == null) return 0.0; if (talkTextBox == null) return 0.0; tuneTab.EmulateChangeSelectedIndex(1); ApplyEffectParameters(); ApplyEmotionParameters(); if (!saveButton.IsEnabled) { Console.WriteLine("a:wait.."); while (!saveButton.IsEnabled) { Thread.Sleep(10); } Console.WriteLine("a:finish"); } talkTextBox.EmulateChangeText(TalkText); Thread.Sleep(100); //VOICEROID2 Editorの音声保存ボタンを押す saveButton.EmulateClick(new Async()); bool finish_savefileSetup = false; bool skip_saveOptionDlg = false; while (finish_savefileSetup == false) { //音声保存の設定ダイアログ処理 var saveDlgs = WindowControl.GetFromWindowText(_app, "音声保存"); try { if ((!skip_saveOptionDlg)&&(saveDlgs.Length != 0)) { //OKボタンを押す WPFButtonBase btn = new WPFButtonBase( saveDlgs[0].LogicalTree(TreeRunDirection.Descendants).ByType("System.Windows.Controls.Button")[0] ); btn.EmulateClick(new Async()); skip_saveOptionDlg = true; } } catch (Exception) { // } //名前を付けて保存 ダイアログで名前を設定 var fileDlgs = WindowControl.GetFromWindowText(_app, "名前を付けて保存"); try { if ((fileDlgs.Length != 0) && (fileDlgs[0].WindowClassName == "#32770")) { //NativeEdit fileName = new NativeEdit(fileDlgs[0].GetFromWindowClass("Edit")[0]); //NativeButton btn = new NativeButton(fileDlgs[0].GetFromWindowClass("Button")[0]); // https://github.com/mikoto2000/TTSController UI特定の記述を参照 NativeButton btn = 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ボタンを押す btn.EmulateClick(new Async()); finish_savefileSetup = true; } } catch (Exception) { // } Thread.Sleep(10); } bool finish_fileSave = false; while (finish_fileSave == false) { //上書き確認ダイアログの処理2 var overwriteDlgs2 = WindowControl.GetFromWindowText(_app, "ファイル保存"); try { if ((overwriteDlgs2.Length != 0) && (overwriteDlgs2[0].WindowClassName == "#32770")) { //上書きボタンを押す NativeButton btn = new NativeButton(overwriteDlgs2[0].GetFromWindowClass("Button")[0]); btn.EmulateClick(new Async()); } } catch (Exception) { // } // 最後の確認ダイアログの処理 var infoDlgs = WindowControl.GetFromWindowText(_app, "情報"); try { if ((infoDlgs.Length != 0) && (infoDlgs[0].WindowClassName == "#32770")) { //OKボタンを押す NativeButton btn = new NativeButton(infoDlgs[0].IdentifyFromWindowClass("Button")); btn.EmulateClick(new Async()); finish_fileSave = true; } } catch (Exception) { // } Thread.Sleep(10); } return 0.0; } public bool withEmotionParams(int avatorIndex) { if ((avatorIndex < 0) || (avatorIndex > avatorParams.Count)) return false; return avatorUIParams[avatorIndex].withEmotionParams; } public decimal GetSliderValue(VoiceEffect ef) { tuneTab.EmulateChangeSelectedIndex(1); decimal ans = 0.00m; WPFSlider ui1 = null; switch (ef) { case VoiceEffect.volume: ui1 = avatorUIParams[selectedAvator].volumeSlider; break; case VoiceEffect.speed: ui1 = avatorUIParams[selectedAvator].speedSlider; break; case VoiceEffect.pitch: ui1 = avatorUIParams[selectedAvator].pitchSlider; break; case VoiceEffect.intonation: ui1 = avatorUIParams[selectedAvator].intonationSlider; break; } ans = Convert.ToDecimal(ui1.Value); return ans; } public decimal GetSliderValue(string emo) { tuneTab.EmulateChangeSelectedIndex(1); if (!avatorUIParams[selectedAvator].emotionSliderIndexs.ContainsKey(emo)) { throw new Exception("Effect Slider not found"); } WPFSlider ui = avatorUIParams[selectedAvator].emotionSliders[avatorUIParams[selectedAvator].emotionSliderIndexs[emo]]; return Convert.ToDecimal(ui.Value); } protected void ScanPreset(WPFListView avatorListView, int indexbase, int presettabIndex) { for (int i = 0; i < avatorListView.ItemCount; i++) { avatorParam item = new avatorParam(); avatorUIParam uiItem = new avatorUIParam(); item.voiceEmotions = new Dictionary<string, EffectValueInfo>(); item.voiceEmotions_default = new Dictionary<string, EffectValueInfo>(); uiItem.emotionSliderIndexs = new Dictionary<string, int>(); uiItem.emotionSliders = new Dictionary<int, WPFSlider>(); avatorListView.EmulateChangeSelectedIndex(i); tuneTab.EmulateChangeSelectedIndex(1); //プリセット名取得 WPFTextBox nameTextBox = null; try { var params1 = tuneTab.VisualTree(TreeRunDirection.Descendants).ByType("AI.Framework.Wpf.Controls.TextBoxEx")[0]; nameTextBox = new WPFTextBox(params1); } catch (Exception ep1) { DebugMsg(string.Format("params1 exception :{0}", ep1.Message + "\n" + ep1.StackTrace)); throw new Exception(string.Format("*** fail. unknown gui(name capture).")); } DebugMsg(string.Format("presetName:{0}", nameTextBox.Text)); //スライダーの配列を取得(共通) try { var params2 = tuneTab.VisualTree(TreeRunDirection.Descendants).ByType("System.Windows.Controls.Slider"); uiItem.volumeSlider = new WPFSlider(params2[0]); uiItem.speedSlider = new WPFSlider(params2[1]); uiItem.pitchSlider = new WPFSlider(params2[2]); uiItem.intonationSlider = new WPFSlider(params2[3]); item.voiceEffects_default = new Dictionary<VoiceEffect, EffectValueInfo> { {VoiceEffect.volume, new EffectValueInfo(Convert.ToDecimal(uiItem.volumeSlider.Value), Convert.ToDecimal(uiItem.volumeSlider.Minimum), Convert.ToDecimal(uiItem.volumeSlider.Maximum), 0.01m)}, {VoiceEffect.speed, new EffectValueInfo(Convert.ToDecimal(uiItem.speedSlider.Value), Convert.ToDecimal(uiItem.speedSlider.Minimum), Convert.ToDecimal(uiItem.speedSlider.Maximum), 0.01m)}, {VoiceEffect.pitch, new EffectValueInfo(Convert.ToDecimal(uiItem.pitchSlider.Value), Convert.ToDecimal(uiItem.pitchSlider.Minimum), Convert.ToDecimal(uiItem.pitchSlider.Maximum), 0.01m)}, {VoiceEffect.intonation, new EffectValueInfo(Convert.ToDecimal(uiItem.intonationSlider.Value), Convert.ToDecimal(uiItem.intonationSlider.Minimum), Convert.ToDecimal(uiItem.intonationSlider.Maximum), 0.01m)} }; item.voiceEffects = new Dictionary<VoiceEffect, EffectValueInfo> { {VoiceEffect.volume, new EffectValueInfo(Convert.ToDecimal(uiItem.volumeSlider.Value), Convert.ToDecimal(uiItem.volumeSlider.Minimum), Convert.ToDecimal(uiItem.volumeSlider.Maximum), 0.01m)}, {VoiceEffect.speed, new EffectValueInfo(Convert.ToDecimal(uiItem.speedSlider.Value), Convert.ToDecimal(uiItem.speedSlider.Minimum), Convert.ToDecimal(uiItem.speedSlider.Maximum), 0.01m)}, {VoiceEffect.pitch, new EffectValueInfo(Convert.ToDecimal(uiItem.pitchSlider.Value), Convert.ToDecimal(uiItem.pitchSlider.Minimum), Convert.ToDecimal(uiItem.pitchSlider.Maximum), 0.01m)}, {VoiceEffect.intonation, new EffectValueInfo(Convert.ToDecimal(uiItem.intonationSlider.Value), Convert.ToDecimal(uiItem.intonationSlider.Minimum), Convert.ToDecimal(uiItem.intonationSlider.Maximum), 0.01m)} }; } catch (Exception ep2) { DebugMsg(string.Format("params2 exception :{0}", ep2.Message + "\n" + ep2.StackTrace)); throw new Exception(string.Format("*** fail. unknown gui(LinearFader capture).")); } //スライダーの配列を取得(スタイル) try { var params3 = tuneTab.VisualTree(TreeRunDirection.Descendants).ByType("System.Windows.Controls.ListBox").Single(); WPFListBox s1 = new WPFListBox(params3); if ((s1 != null) && (s1.ItemCount != 0)) { for (int sidx = 0; sidx < s1.ItemCount; sidx++) { var sitem = s1.GetItem(sidx); var textblocks = sitem.VisualTree().ByType("System.Windows.Controls.TextBlock"); int nameBlockIdx = textblocks.Count > 2 ? (textblocks.Count - 2) : 0; WPFSlider slider = new WPFSlider(sitem.VisualTree().ByType("AI.Framework.Wpf.Controls.LinearFader").Single()); WPFTextBlock emoname = new WPFTextBlock(textblocks[nameBlockIdx]); item.voiceEmotions_default.Add(emoname.Text, new EffectValueInfo(Convert.ToDecimal(slider.Value), Convert.ToDecimal(slider.Minimum), Convert.ToDecimal(slider.Maximum), 0.01m)); item.voiceEmotions.Add(emoname.Text, new EffectValueInfo(Convert.ToDecimal(slider.Value), Convert.ToDecimal(slider.Minimum), Convert.ToDecimal(slider.Maximum), 0.01m)); uiItem.emotionSliderIndexs.Add(emoname.Text, sidx); uiItem.emotionSliders.Add(sidx, slider); // 将来のため保持しているだけ。 } uiItem.withEmotionParams = true; } } catch (Exception ep3) { DebugMsg(string.Format("params3 exception :{0}", ep3.Message + "\n" + ep3.StackTrace)); throw new Exception(string.Format("*** fail. unknown gui(Style LinearFader capture).")); } uiItem.presetTabIndex = presettabIndex; avatorUIParams.Add(indexbase + i, uiItem); item.avatorIndex = i; item.avatorName = nameTextBox.Text; avatorParams.Add(indexbase + i, item); } } protected void ApplyEmotionParameters() { if (avatorParams.Count == 0) return; if (avatorUIParams[selectedAvator].withEmotionParams) { tuneTab.EmulateChangeSelectedIndex(1); //ボイスタブ WPFListBox emoList = new WPFListBox(tuneTab.VisualTree(TreeRunDirection.Descendants).ByType("System.Windows.Controls.ListBox").Single()); foreach (KeyValuePair<string, EffectValueInfo> item in avatorParams[selectedAvator].voiceEmotions) { double p = Convert.ToDouble(avatorParams[selectedAvator].voiceEmotions[item.Key].value); int eidx = avatorUIParams[selectedAvator].emotionSliderIndexs[item.Key]; var sitem = emoList.GetItem(eidx); WPFSlider slider = new WPFSlider(sitem.VisualTree().ByType("AI.Framework.Wpf.Controls.LinearFader").Single()); slider["Value"](p); } } } protected void ApplyEffectParameters() { if (avatorParams.Count == 0) return; tuneTab.EmulateChangeSelectedIndex(1); WPFSlider ui1 = null; foreach (KeyValuePair<VoiceEffect, EffectValueInfo> item in avatorParams[selectedAvator].voiceEffects) { switch (item.Key) { case VoiceEffect.volume: ui1 = avatorUIParams[selectedAvator].volumeSlider; break; case VoiceEffect.speed: ui1 = avatorUIParams[selectedAvator].speedSlider; break; case VoiceEffect.pitch: ui1 = avatorUIParams[selectedAvator].pitchSlider; break; case VoiceEffect.intonation: ui1 = avatorUIParams[selectedAvator].intonationSlider; break; } double p = Convert.ToDouble(avatorParams[selectedAvator].voiceEffects[item.Key].value); if (ui1 != null) ui1.EmulateChangeValue(p); } } } }
リクエストがあったので掲載。
using System; using System.Collections.Generic; using System.Threading; using System.Diagnostics; using Codeer.Friendly.Windows; using Codeer.Friendly.Windows.Grasp; using Ong.Friendly.FormsStandardControls; using Codeer.Friendly; using Codeer.Friendly.Windows.NativeStandardControls; namespace SeikaCenter { class VoiceroidEx : ProductControlBase { Dictionary<int, avatorUIParam> avatorUIParams; class avatorUIParam { public WindowsAppFriend _app = null; public WindowControl uiTreeTop = null; public WindowControl talkTextBox = null; public FormsButton playButton = null; public FormsButton saveButton = null; public FormsTextBox volumeText = null; public FormsTextBox speedText = null; public FormsTextBox pitchText = null; public FormsTextBox intonationText = null; } protected override void Dispose(bool disposing) { if (this.disposed) return; if (disposing) { if (avatorUIParams != null) { //foreach (KeyValuePair<int, avatorUIParam> item in avatorUIParams) //{ // item.Value._app?.Dispose(); //} } } this.disposed = true; base.Dispose(disposing); } public VoiceroidEx() { int index = 0; avatorParams = new Dictionary<int, avatorParam>(); avatorUIParams = new Dictionary<int, avatorUIParam>(); Dictionary<Avator,Process> p = GetVoiceroidProcess(); aliveInstance = false; foreach (KeyValuePair<Avator, Process> v in p) { avatorParam item = new avatorParam(); avatorUIParam uiItem = new avatorUIParam(); try { uiItem._app = new WindowsAppFriend(v.Value); uiItem.uiTreeTop = WindowControl.FromZTop(uiItem._app); // Zインデクスはコーディア様の TestAssistantで確認可能 // 音声効果タブ切替 //FormsTabControl a6 = new FormsTabControl(uiItem.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0)); dynamic a6 = new FormsTabControl(uiItem.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0)); a6.EmulateTabSelect(2); uiItem.talkTextBox = uiItem.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 1); uiItem.playButton = new FormsButton(uiItem.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 0, 3)); uiItem.saveButton = new FormsButton(uiItem.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 1, 0, 1, 0, 1)); uiItem.volumeText = new FormsTextBox(uiItem.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0, 0, 0, 8)); uiItem.speedText = new FormsTextBox(uiItem.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0, 0, 0, 9)); uiItem.pitchText = new FormsTextBox(uiItem.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0, 0, 0, 10)); uiItem.intonationText = new FormsTextBox(uiItem.uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0, 0, 0, 11)); avatorUIParams.Add(index, uiItem); item.avatorIndex = index; item.avatorName = string.Format("{0}", v.Key.ProdName()); item.voiceEffects_default = new Dictionary<VoiceEffect, EffectValueInfo> { {VoiceEffect.volume, new EffectValueInfo(GetSliderValue(VoiceEffect.volume), 0.0m, 2.0m, 0.01m)}, {VoiceEffect.speed, new EffectValueInfo(GetSliderValue(VoiceEffect.speed), 0.5m, 4.0m, 0.01m)}, {VoiceEffect.pitch, new EffectValueInfo(GetSliderValue(VoiceEffect.pitch), 0.5m, 2.0m, 0.01m)}, {VoiceEffect.intonation, new EffectValueInfo(GetSliderValue(VoiceEffect.intonation), 0.0m, 2.0m, 0.01m)} }; item.voiceEffects = new Dictionary<VoiceEffect, EffectValueInfo> { {VoiceEffect.volume, new EffectValueInfo(GetSliderValue(VoiceEffect.volume), 0.0m, 2.0m, 0.01m)}, {VoiceEffect.speed, new EffectValueInfo(GetSliderValue(VoiceEffect.speed), 0.5m, 4.0m, 0.01m)}, {VoiceEffect.pitch, new EffectValueInfo(GetSliderValue(VoiceEffect.pitch), 0.5m, 2.0m, 0.01m)}, {VoiceEffect.intonation, new EffectValueInfo(GetSliderValue(VoiceEffect.intonation), 0.0m, 2.0m, 0.01m)} }; item.voiceEmotions = new Dictionary<string, EffectValueInfo>(); item.voiceEmotions_default = new Dictionary<string, EffectValueInfo>(); avatorParams.Add(index, item); index++; } catch (Exception egg) { Console.WriteLine("egg:{0},{1}", egg.Message,egg.StackTrace); } } aliveInstance = avatorParams.Count != 0 ? true : false; } public override bool SelectAvator(int avatorIndex) { if ((avatorIndex < 0) || (avatorIndex > avatorParams.Count)) return false; selectedAvator = avatorIndex; return true; } public override double Play() { Stopwatch sw = new Stopwatch(); if (avatorUIParams[selectedAvator].playButton == null) return 0.0; if (avatorUIParams[selectedAvator].saveButton == null) return 0.0; if (avatorUIParams[selectedAvator].talkTextBox == null) return 0.0; dynamic a6 = new FormsTabControl(avatorUIParams[selectedAvator].uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0)); a6.EmulateTabSelect(2); ApplyEffectParameters(); ApplyEmotionParameters(); // 再生中なので再生終了を待つ(音声保存ボタンがEnableになるのを待つ) if (!avatorUIParams[selectedAvator].saveButton.Enabled) { Console.WriteLine("a:wait.."); while (!avatorUIParams[selectedAvator].saveButton.Enabled) { Thread.Sleep(10); } Console.WriteLine("a:finish"); } avatorUIParams[selectedAvator].talkTextBox["Text"](TalkText); Thread.Sleep(100); sw.Start(); avatorUIParams[selectedAvator].playButton.EmulateClick(); // 再生開始を待つ(音声保存ボタンがDisableになるのを待つ) if (avatorUIParams[selectedAvator].saveButton.Enabled) { Console.WriteLine("b:wait.."); while (avatorUIParams[selectedAvator].saveButton.Enabled) { Thread.Sleep(10); } Console.WriteLine("b:finish"); } // 再生終了を待つ(音声保存ボタンがEnableになるのを待つ) if (!avatorUIParams[selectedAvator].saveButton.Enabled) { Console.WriteLine("c:wait.."); while (!avatorUIParams[selectedAvator].saveButton.Enabled) { Thread.Sleep(10); } Console.WriteLine("c:finish"); } sw.Stop(); return sw.ElapsedMilliseconds; } public override double SaveFromProduct(string saveFilename) { if (avatorUIParams[selectedAvator].playButton == null) return 0.0; if (avatorUIParams[selectedAvator].saveButton == null) return 0.0; if (avatorUIParams[selectedAvator].talkTextBox == null) return 0.0; dynamic a6 = new FormsTabControl(avatorUIParams[selectedAvator].uiTreeTop.IdentifyFromZIndex(2, 0, 0, 0, 0)); a6.EmulateTabSelect(2); ApplyEffectParameters(); ApplyEmotionParameters(); if (!avatorUIParams[selectedAvator].saveButton.Enabled) { Console.WriteLine("a:wait.."); while (!avatorUIParams[selectedAvator].saveButton.Enabled) { Thread.Sleep(10); } Console.WriteLine("a:finish"); } avatorUIParams[selectedAvator].talkTextBox["Text"](TalkText); Thread.Sleep(100); avatorUIParams[selectedAvator].saveButton.EmulateClick(new Async()); bool finish_savefileSetup = false; while (finish_savefileSetup == false) { //名前を付けて保存 ダイアログで名前を設定 var fileDlgs = WindowControl.GetFromWindowText(avatorUIParams[selectedAvator]._app, "音声ファイルの保存"); try { if ((fileDlgs.Length != 0) && (fileDlgs[0].WindowClassName == "#32770")) { //NativeEdit fileName = new NativeEdit(fileDlgs[0].GetFromWindowClass("Edit")[0]); //NativeButton btn = new NativeButton(fileDlgs[0].GetFromWindowClass("Button")[0]); // https://github.com/mikoto2000/TTSController UI特定の記述を参照 NativeButton btn = 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ボタンを押す btn.EmulateClick(new Async()); finish_savefileSetup = true; } } catch (Exception) { // } Thread.Sleep(10); } return 0.0; } public decimal GetSliderValue(VoiceEffect ef) { decimal ans = 0.00m; FormsTextBox ui1 = null; switch (ef) { case VoiceEffect.volume: ui1 = avatorUIParams[selectedAvator].volumeText; break; case VoiceEffect.speed: ui1 = avatorUIParams[selectedAvator].speedText; break; case VoiceEffect.pitch: ui1 = avatorUIParams[selectedAvator].pitchText; break; case VoiceEffect.intonation: ui1 = avatorUIParams[selectedAvator].intonationText; break; } ans = Convert.ToDecimal(ui1.Text); return ans; } private void ApplyEmotionParameters() { if (avatorParams.Count == 0) return; } private void ApplyEffectParameters() { if (avatorParams.Count == 0) return; FormsTextBox ui1 = null; foreach (KeyValuePair<VoiceEffect, EffectValueInfo> item in avatorParams[selectedAvator].voiceEffects) { switch (item.Key) { case VoiceEffect.volume: ui1 = avatorUIParams[selectedAvator].volumeText; break; case VoiceEffect.speed: ui1 = avatorUIParams[selectedAvator].speedText; break; case VoiceEffect.pitch: ui1 = avatorUIParams[selectedAvator].pitchText; break; case VoiceEffect.intonation: ui1 = avatorUIParams[selectedAvator].intonationText; break; } double p = Convert.ToDouble(avatorParams[selectedAvator].voiceEffects[item.Key].value); if (ui1 != null) ui1.EmulateChangeText(string.Format("{0:0.00}", p)); } } private Dictionary<Avator, Process> GetVoiceroidProcess() { Process[] ps = Process.GetProcesses(); Dictionary<Avator, Process> p = new Dictionary<Avator, Process>(); foreach (Avator item in Enum.GetValues(typeof(Avator))) { string winTitle1 = item.ProdName(); string winTitle2 = winTitle1 + "*"; foreach (Process pitem in ps) { if ((pitem.MainWindowHandle != IntPtr.Zero) && ((pitem.MainWindowTitle.Equals(winTitle1)) || (pitem.MainWindowTitle.Equals(winTitle2)))) { p.Add(item, pitem); break; } } } return p; } } enum Avator { SEIKA, YOSHIDA_EX, AI_EX, SHOUTA_EX, MINASE, KIRITAN, ZUNKO, ZUNKO_EX, TAMMY_EX, YUKARI_EX, AKANE, AOI, UNA, GALACO1, GALACO2 } static class AvatorEx { public static string ProdName(this Avator value) { string[] values = { "VOICEROID+ 京町セイカ EX", "VOICEROID+ 鷹の爪 吉田くん EX", "VOICEROID+ 月読アイ EX", "VOICEROID+ 月読ショウタ EX", "VOICEROID+ 水奈瀬コウ EX", "VOICEROID+ 東北きりたん EX", "VOICEROID+ 東北ずん子", "VOICEROID+ 東北ずん子 EX", "VOICEROID+ 民安ともえ EX", "VOICEROID+ 結月ゆかり EX", "VOICEROID+ 琴葉茜", "VOICEROID+ 琴葉葵", "音街ウナTalk Ex", "ギャラ子Talk", "ギャラ子 Talk" }; return values[(int)value]; } } }
AITalkEditor2Base.cs への統合分離の真っ最中です。ダブっているメソッドや処理がかなりあります。
UIコンポーネントの特定方法はかなりいい加減です。将来のアップデートで対応できなくなってしまう可能性があります。
ボイスタブのスタイルで表示されている喜び、怒り、悲しみのパラメタですが、これらは1度レンダリングされたら維持されるものではなくて毎回作られる模様です。よくわかりません。
using System; using System.Collections.Generic; using System.Threading; using System.Diagnostics; using Codeer.Friendly; using Codeer.Friendly.Windows; using Codeer.Friendly.Dynamic; using Codeer.Friendly.Windows.Grasp; using RM.Friendly.WPFStandardControls; using Codeer.Friendly.Windows.NativeStandardControls; namespace SeikaCenter { class Voiceroid2 : AITalkEditor2Base { public Voiceroid2() { // } public void Voiceroid2Init() { avatorParams = new Dictionary<int, avatorParam>(); avatorUIParams = new Dictionary<int, avatorUIParam>(); Process p = GetVoiceroidEditorProcess(); aliveInstance = false; if (p != null) { try { _app = new WindowsAppFriend(p); uiTreeTop = WindowControl.FromZTop(_app); //判明しているGUI要素特定 var editUis = uiTreeTop.GetFromTypeFullName("AI.Talk.Editor.TextEditView")[0].LogicalTree(); talkTextBox = new WPFTextBox(editUis[4]); playButton = new WPFButtonBase(editUis[6]); saveButton = new WPFButtonBase(editUis[24]); var tabs = uiTreeTop.GetFromTypeFullName("AI.Framework.Wpf.Controls.TitledTabControl"); voicePresetTab = new WPFTabControl(tabs[0]); // ボイス(プリセット)のタブコントロール tuneTab = new WPFTabControl(tabs[1]); // チューニングのタブコントロール //標準タブにいる各話者毎のGUI要素データを取得 tuneTab.EmulateChangeSelectedIndex(1); voicePresetTab.EmulateChangeSelectedIndex(0); avatorListView_std = new WPFListView(uiTreeTop.GetFromTypeFullName("System.Windows.Controls.ListView")[0]); ScanPreset(avatorListView_std, 0, 0); //ユーザータブにいる各話者プリセット毎のGUI要素データを取得 tuneTab.EmulateChangeSelectedIndex(1); voicePresetTab.EmulateChangeSelectedIndex(1); avatorListView_usr = new WPFListView(uiTreeTop.GetFromTypeFullName("System.Windows.Controls.ListView")[1]); ScanPreset(avatorListView_usr, avatorListView_std.ItemCount, 1); aliveInstance = true; } catch (Exception ev2) { DebugMsg(string.Format("Catch Exception.")); DebugMsg(string.Format(ev2.Message + ev2.StackTrace)); Console.WriteLine("ev2:{0}", ev2.Message + ev2.StackTrace); aliveInstance = false; } } else { DebugMsg(string.Format("VOICEROID2 Window handle not detected.")); } } private Process GetVoiceroidEditorProcess() { string winTitle1 = "VOICEROID2"; string winTitle2 = winTitle1 + "*"; int RetryCount = 3; int RetryWaitms = 500; Process p = null; for (int i = 0; i < 3; i++) { Process[] ps = Process.GetProcesses(); foreach (Process pitem in ps) { if ((pitem.MainWindowHandle != IntPtr.Zero) && ((pitem.MainWindowTitle.Equals(winTitle1)) || (pitem.MainWindowTitle.Equals(winTitle2)))) { p = pitem; if (i < (RetryCount - 1)) Thread.Sleep(RetryWaitms); } } } return p; } } }
リクエストがあったので掲載。外部連携インタフェースが用意されているのはありがたい。
CeVIOを使う場合、2018/06/04時点では32bit版のバイナリとして作る必要がある。
CeVIO専用なら遅延バインドする必要はない。CeVIOが存在しない環境が考えられるなら遅延バインドしないといけない。
遅延バインド時はAppDomainを使って外部連携インタフェースのアセンブリを解放可能にしておく必要がある。
using System; using System.Collections.Generic; using System.Reflection; using System.Diagnostics; namespace SeikaCenter { class Cevio : ProductControlBase { CevioProxy cevioTalker = null; string Version { get; } AppDomain appDomain = null; private bool disposed = false; protected override void Dispose(bool disposing) { if (this.disposed) return; if (disposing) { //if (appDomain != null) //{ // try // { // AppDomain.Unload(appDomain); // } // finally // { // appDomain = null; // } //} } this.disposed = true; base.Dispose(disposing); } public Cevio(string databaseFilename) { avatorParams = new Dictionary<int, avatorParam>(); try { appDomain = AppDomain.CreateDomain("SeikaCenterCevioPlugin"); Type t2 = typeof(CevioProxy); cevioTalker = (CevioProxy)appDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, t2.FullName); dynamic talkers = cevioTalker.AvailableCasts; if (talkers.Length !=0) { for (int i = 0; i < talkers.Length; i++) { avatorParam item = new avatorParam(); cevioTalker.Cast = talkers[i]; item.avatorIndex = i; item.avatorName = talkers[i]; item.voiceEffects_default = new Dictionary<VoiceEffect, EffectValueInfo> { {VoiceEffect.volume, new EffectValueInfo( (decimal)cevioTalker.Volume, 0.0m, 100.0m, 1.00m)}, {VoiceEffect.speed, new EffectValueInfo( (decimal)cevioTalker.Speed, 0.0m, 100.0m, 1.00m)}, {VoiceEffect.pitch, new EffectValueInfo( (decimal)cevioTalker.Tone, 0.0m, 100.0m, 1.00m)}, {VoiceEffect.alpha, new EffectValueInfo( (decimal)cevioTalker.Alpha, 0.0m, 100.0m, 1.00m)}, {VoiceEffect.intonation, new EffectValueInfo( (decimal)cevioTalker.ToneScale, 0.0m, 100.0m, 1.00m)} }; item.voiceEffects = new Dictionary<VoiceEffect, EffectValueInfo> { {VoiceEffect.volume, new EffectValueInfo( (decimal)cevioTalker.Volume, 0.0m, 100.0m, 1.00m)}, {VoiceEffect.speed, new EffectValueInfo( (decimal)cevioTalker.Speed, 0.0m, 100.0m, 1.00m)}, {VoiceEffect.pitch, new EffectValueInfo( (decimal)cevioTalker.Tone, 0.0m, 100.0m, 1.00m)}, {VoiceEffect.alpha, new EffectValueInfo( (decimal)cevioTalker.Alpha, 0.0m, 100.0m, 1.00m)}, {VoiceEffect.intonation, new EffectValueInfo( (decimal)cevioTalker.ToneScale, 0.0m, 100.0m, 1.00m)} }; item.voiceEmotions_default = new Dictionary<string, EffectValueInfo>(); item.voiceEmotions = new Dictionary<string, EffectValueInfo>(); Dictionary<string, uint> emoparams = cevioTalker.Components; if (0 < emoparams.Count) { foreach (KeyValuePair<string, uint> emo in emoparams) { item.voiceEmotions_default.Add(emo.Key, new EffectValueInfo((decimal)(emo.Value), 0.00m, 100.0m, 1.00m)); item.voiceEmotions.Add(emo.Key, new EffectValueInfo((decimal)(emo.Value), 0.00m, 100.0m, 1.00m)); } } avatorParams.Add(i, item); } aliveInstance = true; } } catch(Exception e) { Console.WriteLine("{0},{1}", e.Message,e.StackTrace); aliveInstance = false; } } public override bool SelectAvator(int avatorIndex) { if ((avatorIndex < 0) || (avatorIndex > avatorParams.Count)) return false; selectedAvator = avatorIndex; cevioTalker.Cast = avatorParams[avatorIndex].avatorName; return true; } public override double Play(bool async = false) { Stopwatch sw = new Stopwatch(); ApplyEffectParameters(); ApplyEmotionParameters(); sw.Start(); try { cevioTalker.Speak(TalkText, async); } catch (Exception) { // } sw.Stop(); return sw.ElapsedMilliseconds; } public override double SaveFromProduct(string saveFilename) { ApplyEffectParameters(); ApplyEmotionParameters(); try { cevioTalker.Save(TalkText, saveFilename); } catch (Exception) { // } return 0.0; } private void ApplyEmotionParameters() { if (avatorParams.Count != 0) { foreach (KeyValuePair<string, EffectValueInfo> item in avatorParams[selectedAvator].voiceEmotions) { cevioTalker.SetComponent(item.Key, (uint)(item.Value.value)); } } } private void ApplyEffectParameters() { if (avatorParams.Count != 0) { if (!KeepEmotionSetting) { foreach (KeyValuePair<VoiceEffect, EffectValueInfo> item in avatorParams[selectedAvator].voiceEffects_default) { switch (item.Key) { case VoiceEffect.volume: cevioTalker.Volume = (uint)(item.Value.value); break; case VoiceEffect.speed: cevioTalker.Speed = (uint)(item.Value.value); break; case VoiceEffect.pitch: cevioTalker.Tone = (uint)(item.Value.value); break; case VoiceEffect.alpha: cevioTalker.Alpha = (uint)(item.Value.value); break; case VoiceEffect.intonation: cevioTalker.ToneScale = (uint)(item.Value.value); break; } } } foreach (KeyValuePair<VoiceEffect, EffectValueInfo> item in avatorParams[selectedAvator].voiceEffects) { switch (item.Key) { case VoiceEffect.volume: cevioTalker.Volume = (uint)(item.Value.value); break; case VoiceEffect.speed: cevioTalker.Speed = (uint)(item.Value.value); break; case VoiceEffect.pitch: cevioTalker.Tone = (uint)(item.Value.value); break; case VoiceEffect.alpha: cevioTalker.Alpha = (uint)(item.Value.value); break; case VoiceEffect.intonation: cevioTalker.ToneScale = (uint)(item.Value.value); break; } } } } } // このクラス内で遅延バインドさせる public class CevioProxy : MarshalByRefObject { dynamic cevioTalker; double timeout = 3 * 60 * 1000; public CevioProxy() { Type t2 = Type.GetTypeFromProgID("CeVIO.Talk.RemoteService.Talker"); // 遅延バインドで使用します cevioTalker = Activator.CreateInstance(t2); } public override object InitializeLifetimeService() { return null; } public string[] AvailableCasts { get { List<string> x = new List<string>(); dynamic ac = cevioTalker.AvailableCasts; for (int i = 0; i < ac.Length; i++) { x.Add(ac[i]); } return x.ToArray(); } } public string Cast { get { return cevioTalker.Cast; } set { cevioTalker.Cast = value; } } public uint Volume { get { return cevioTalker.Volume; } set { cevioTalker.Volume = value; } } public uint Speed { get { return cevioTalker.Speed; } set { cevioTalker.Speed = value; } } public uint Tone { get { return cevioTalker.Tone; } set { cevioTalker.Tone = value; } } public uint Alpha { get { return cevioTalker.Alpha; } set { cevioTalker.Alpha = value; } } public uint ToneScale { get { return cevioTalker.ToneScale; } set { cevioTalker.ToneScale = value; } } public Dictionary<string, uint> Components { get { var emo = cevioTalker.Components; Dictionary<string, uint> param = new Dictionary<string, uint>(); for (int i = 0; i < emo.Length; i++) { param.Add(emo[i].Name, emo[i].Value); } return param; } } public void SetComponent(string emoName, uint emoValue) { cevioTalker.Components[emoName].Value = emoValue; } public string GetComponent(string emoName) { return cevioTalker.Components[emoName].Value; } public void Speak(string text, bool async = false) { dynamic status = cevioTalker.Speak(text); //SpeakingState status = cevioTalker.Speak(TalkText); if (!async) { status.Wait(timeout); } cevioTalker.Stop(); } public void Save(string text, string WavFilePath) { cevioTalker.OutputWaveToFile(text, WavFilePath); } } }
リクエストがあったので掲載。発声に際しては実行するスレッドに注意。
using System; using System.Collections.Generic; using System.Threading; using SpeechLib; using System.Text.RegularExpressions; using System.Diagnostics; namespace SeikaCenter { internal class Sapi5 : ProductControlBase { SpVoice sapi = null; ISpeechObjectTokens speakerList = null; string audioDefaultOutputName = ""; private bool disposed = false; protected override void Dispose(bool disposing) { if (this.disposed) return; if (disposing) { // } this.disposed = true; base.Dispose(disposing); } public Sapi5(string databaseFilename) { avatorParams = new Dictionary<int, avatorParam>(); try { sapi = new SpVoice(); speakerList = sapi.GetVoices("", ""); audioDefaultOutputName = Regex.Replace( sapi.AudioOutput.GetDescription(), @"\n", ""); for (int i = 0; i < speakerList.Count; i++) { avatorParam item = new avatorParam(); item.avatorIndex = i; item.avatorName = speakerList.Item(i).GetAttribute("Name"); item.voiceEffects_default = new Dictionary<VoiceEffect, EffectValueInfo> { {VoiceEffect.volume, new EffectValueInfo(100.0m, 0.0m, 100.0m, 1.00m)}, {VoiceEffect.speed, new EffectValueInfo(0.00m, -10.0m, 10.0m, 1.00m)} }; item.voiceEffects = new Dictionary<VoiceEffect, EffectValueInfo>(item.voiceEffects_default); item.voiceEmotions = new Dictionary<string, EffectValueInfo>(); item.voiceEmotions_default = new Dictionary<string, EffectValueInfo>(); avatorParams.Add(i, item); } aliveInstance = true; } catch (Exception ef5) { Console.WriteLine("ef5:{0},{1},{2}", ef5.Message, ef5.InnerException == null ? "" : ef5.InnerException.Message, ef5.StackTrace); aliveInstance = false; } } public override bool SelectAvator(int avatorIndex) { if ((avatorIndex < 0) || (avatorIndex > avatorParams.Count)) return false; selectedAvator = avatorIndex; sapi.Voice = speakerList.Item(avatorIndex); return true; } public override double Play(bool async = false) { Stopwatch sw = new Stopwatch(); ApplyEmotionParameters(); ApplyEffectParameters(); var audioOutList = sapi.GetAudioOutputs(); int audioOutoutIdx = -1; int audioDefaultOutoutIdx = -1; for (int i = 0; i < audioOutList.Count; i++) { string name = Regex.Replace(audioOutList.Item(i).GetDescription(), @"\n", ""); if (name == audioDefaultOutputName) { audioDefaultOutoutIdx = i; } if (name == CapDevFriendlyName) { audioOutoutIdx = i; break; } } sapi.AllowAudioOutputFormatChangesOnNextSet = true; if (audioOutoutIdx != -1) { sapi.AudioOutput = audioOutList.Item(audioOutoutIdx); } else { sapi.AudioOutput = audioOutList.Item(audioDefaultOutoutIdx); } sw.Start(); try { if (!async) { Thread t = new Thread(() => { sapi.Speak(TalkText); }); t.SetApartmentState(ApartmentState.STA); t.Start(); t.Join(); } else { Thread t = new Thread(() => { sapi.Speak(TalkText, SpeechVoiceSpeakFlags.SVSFlagsAsync); }); t.SetApartmentState(ApartmentState.STA); t.Start(); } } catch (Exception efe) { Console.WriteLine("efe:{0},{1},{2}", efe.Message, efe.InnerException == null ? "" : efe.InnerException.Message, efe.StackTrace); } sw.Stop(); ResetVoiceEffect(); ResetVoiceEmotion(); ApplyEffectParameters(); ApplyEmotionParameters(); return sw.ElapsedMilliseconds; } public override double SaveFromProduct(string saveFilename) { ApplyEmotionParameters(); ApplyEffectParameters(); try { SpFileStream ss = new SpFileStream(); ss.Open(saveFilename, SpeechStreamFileMode.SSFMCreateForWrite); sapi.AudioOutputStream = ss; Thread t = new Thread(() => { sapi.Speak(TalkText); }); t.SetApartmentState(ApartmentState.STA); t.Start(); t.Join(); ss.Close(); } catch (Exception) { // } ResetVoiceEffect(); ResetVoiceEmotion(); ApplyEffectParameters(); ApplyEmotionParameters(); return 0.0; } private void ApplyEmotionParameters() { } private void ApplyEffectParameters() { if (avatorParams.Count != 0) { foreach (KeyValuePair<VoiceEffect, EffectValueInfo> item in avatorParams[selectedAvator].voiceEffects) { switch (item.Key) { case VoiceEffect.volume: sapi.Volume = (int)item.Value.value; break; case VoiceEffect.speed: sapi.Rate = (int)item.Value.value; break; } } } } } }