/// <summary> /// 音声変換時のデータコールバックメソッド /// </summary> /// <param name="reason">呼び出し要因</param> /// <param name="job_id">ジョブID</param> /// <param name="tick">時刻[ms]</param> /// <param name="user_data">ユーザーデータ(SpeechJobDataへのポインタ)</param> /// <returns>ゼロを返す</returns> private static int RawBufferCallback(AitalkCore.EventReason reason, int job_id, long tick, IntPtr user_data) { GCHandle gc_handle = GCHandle.FromIntPtr(user_data); SpeechJobData job_data = gc_handle.Target as SpeechJobData; if (job_data == null) { return(0); } // 変換できた分だけGetData()で読み取ってjob_dataのバッファに格納する int buffer_capacity = job_data.BufferCapacity; byte[] buffer = new byte[2 * buffer_capacity]; AitalkCore.Result result; int read_samples; do { result = AitalkCore.GetData(job_id, buffer, buffer_capacity, out read_samples); if (result != AitalkCore.Result.Success) { break; } job_data.Output.AddRange(new ArraySegment <byte>(buffer, 0, 2 * read_samples)); }while ((buffer_capacity - 1) <= read_samples); if (reason == AitalkCore.EventReason.RawBufferClose) { job_data.CloseEvent.Set(); } return(0); }
/// <summary> /// パラメータを設定する。 /// param.Sizeおよびparam.NumberOfSpeakersは自動的に設定される。 /// </summary> /// <param name="tts_param">パラメータ(話者パラメータを除く)</param> /// <param name="speaker_params">話者パラメータ</param> private static void SetParameters(AitalkCore.TtsParam tts_param, AitalkCore.TtsParam.SpeakerParam[] speaker_params) { // パラメータを格納するバッファを確保する int size = Marshal.SizeOf <AitalkCore.TtsParam>() + Marshal.SizeOf <AitalkCore.TtsParam.SpeakerParam>() * speaker_params.Length; IntPtr ptr = Marshal.AllocCoTaskMem(size); try { // パラメータを設定する tts_param.Size = size; tts_param.NumberOfSpeakers = speaker_params.Length; Marshal.StructureToPtr <AitalkCore.TtsParam>(tts_param, ptr, false); for (int index = 0; index < speaker_params.Length; index++) { IntPtr speaker_ptr = IntPtr.Add(ptr, Marshal.SizeOf <AitalkCore.TtsParam>() + Marshal.SizeOf <AitalkCore.TtsParam.SpeakerParam>() * index); Marshal.StructureToPtr <AitalkCore.TtsParam.SpeakerParam>(speaker_params[index], speaker_ptr, false); } AitalkCore.Result result; result = AitalkCore.SetParam(ptr); if (result != AitalkCore.Result.Success) { throw new AitalkException("動作パラメータの設定に失敗しました。", result); } } finally { Marshal.FreeCoTaskMem(ptr); } }
/// <summary> /// ボイスライブラリを読み込む /// </summary> /// <param name="voice_db_name">ボイスライブラリ名</param> public static void LoadVoice(string voice_db_name) { if (voice_db_name == CurrentVoice) { return; } CurrentVoice = null; AitalkCore.VoiceClear(); if (voice_db_name == null) { return; } AitalkCore.Result result; result = AitalkCore.VoiceLoad(voice_db_name); if (result != AitalkCore.Result.Success) { throw new AitalkException($"ボイスライブラリ'{voice_db_name}'の読み込みに失敗しました。", result); } // パラメータを読み込む GetParameters(out var tts_param, out var speaker_params); tts_param.TextBufferCallback = TextBufferCallback; tts_param.RawBufferCallback = RawBufferCallback; tts_param.TtsEventCallback = TtsEventCallback; tts_param.PauseBegin = 0; tts_param.PauseTerm = 0; tts_param.ExtendFormatFlags = AitalkCore.ExtendFormat.JeitaRuby | AitalkCore.ExtendFormat.AutoBookmark; Parameter = new AitalkParameter(voice_db_name, tts_param, speaker_params); CurrentVoice = voice_db_name; }
/// <summary> /// 言語ライブラリを読み込む /// </summary> /// <param name="language_name">言語名</param> public static void LoadLanguage(string language_name) { if (language_name == CurrentLanguage) { return; } // 言語の設定をする際はカレントディレクトリを一時的にVOICEROID2のインストールディレクトリに変更する // それ以外ではLangLoad()はエラーを返す string current_directory = System.IO.Directory.GetCurrentDirectory(); System.IO.Directory.SetCurrentDirectory(InstallDirectory); CurrentLanguage = null; AitalkCore.Result result; result = AitalkCore.LangClear(); if ((result == AitalkCore.Result.Success) || (result == AitalkCore.Result.NotLoaded)) { result = AitalkCore.LangLoad($"{InstallDirectory}\\Lang\\{language_name}"); } System.IO.Directory.SetCurrentDirectory(current_directory); if (result != AitalkCore.Result.Success) { throw new AitalkException($"言語'{language_name}'の読み込みに失敗しました。", result); } CurrentLanguage = language_name; }
/// <summary> /// AITalkを終了する /// </summary> public static void Finish() { if (IsInitialized == true) { IsInitialized = false; AitalkCore.End(); } CurrentLanguage = null; CurrentVoice = null; }
/// <summary> /// テキストを読み仮名に変換する /// </summary> /// <param name="text">テキスト</param> /// <param name="Timeout">タイムアウト[ms]。0以下はタイムアウト無しで待ち続ける。</param> /// <returns>読み仮名文字列</returns> public static string TextToKana(string text, int timeout = 0) { UpdateParameter(); // ShiftJISに変換する UnicodeToShiftJis(text, out byte[] shiftjis_bytes, out int[] shiftjis_positions); // コールバックメソッドとの同期オブジェクトを用意する KanaJobData job_data = new KanaJobData(); job_data.BufferCapacity = 0x1000; job_data.Output = new List <byte>(); job_data.CloseEvent = new EventWaitHandle(false, EventResetMode.ManualReset); GCHandle gc_handle = GCHandle.Alloc(job_data); try { // 変換を開始する AitalkCore.JobParam job_param; job_param.ModeInOut = AitalkCore.JobInOut.PlainToKana; job_param.UserData = GCHandle.ToIntPtr(gc_handle); AitalkCore.Result result; result = AitalkCore.TextToKana(out int job_id, ref job_param, shiftjis_bytes); if (result != AitalkCore.Result.Success) { throw new AitalkException($"仮名変換が開始できませんでした。[{string.Join(",", shiftjis_bytes)}]", result); } // 変換の終了を待つ // timeoutで与えられた時間だけ待つ bool respond; respond = job_data.CloseEvent.WaitOne((0 < timeout) ? timeout : -1); // 変換を終了する result = AitalkCore.CloseKana(job_id); if (respond == false) { throw new AitalkException("仮名変換がタイムアウトしました。"); } else if (result != AitalkCore.Result.Success) { throw new AitalkException("仮名変換が正常に終了しませんでした。", result); } } finally { gc_handle.Free(); } // 変換結果に含まれるIrq MARKのバイト位置を文字位置へ置き換える Encoding encoding = Encoding.GetEncoding(932); return(ReplaceIrqMark(encoding.GetString(job_data.Output.ToArray()), shiftjis_positions)); }
/// <summary> /// 記号ポーズ辞書を読み込む /// </summary> /// <param name="path">ファイルパス</param> public static void ReloadSymbolDictionary(string path) { AitalkCore.ReloadSymbolDic(null); if (path == null) { return; } AitalkCore.Result result; result = AitalkCore.ReloadSymbolDic(path); if (result == AitalkCore.Result.UserDictionaryNoEntry) { AitalkCore.ReloadSymbolDic(null); } else if (result != AitalkCore.Result.Success) { throw new AitalkException($"記号ポーズ辞書'{path}'の読み込みに失敗しました。", result); } }
/// <summary> /// パラメータを取得する /// </summary> /// <param name="tts_param">パラメータ(話者パラメータを除く)</param> /// <param name="speaker_params">話者パラメータ</param> private static void GetParameters(out AitalkCore.TtsParam tts_param, out AitalkCore.TtsParam.SpeakerParam[] speaker_params) { // パラメータを格納するのに必要なバッファサイズを取得する AitalkCore.Result result; int size = 0; result = AitalkCore.GetParam(IntPtr.Zero, ref size); if ((result != AitalkCore.Result.Insufficient) || (size < Marshal.SizeOf <AitalkCore.TtsParam>())) { throw new AitalkException("動作パラメータの長さの取得に失敗しました。", result); } IntPtr ptr = Marshal.AllocCoTaskMem(size); try { // パラメータを読み取る Marshal.WriteInt32(ptr, (int)Marshal.OffsetOf <AitalkCore.TtsParam>("Size"), size); result = AitalkCore.GetParam(ptr, ref size); if (result != AitalkCore.Result.Success) { throw new AitalkException("動作パラメータの取得に失敗しました。", result); } tts_param = Marshal.PtrToStructure <AitalkCore.TtsParam>(ptr); // 話者のパラメータを読み取る speaker_params = new AitalkCore.TtsParam.SpeakerParam[tts_param.NumberOfSpeakers]; for (int index = 0; index < speaker_params.Length; index++) { IntPtr speaker_ptr = IntPtr.Add(ptr, Marshal.SizeOf <AitalkCore.TtsParam>() + Marshal.SizeOf <AitalkCore.TtsParam.SpeakerParam>() * index); speaker_params[index] = Marshal.PtrToStructure <AitalkCore.TtsParam.SpeakerParam>(speaker_ptr); } } finally { Marshal.FreeCoTaskMem(ptr); } }
/// <summary> /// AITalkを初期化する /// </summary> /// <param name="install_directory">VOICEROID2のインストールディレクトリ</param> /// <param name="authenticate_code">認証コード</param> public static void Initialize(string install_directory, string authenticate_code) { Finish(); // aitalked.dllをロードするために // DLLの探索パスをVOICEROID2のディレクトリに変更する if ((InstallDirectory != null) && (InstallDirectory != install_directory)) { throw new AitalkException($"インストールディレクトリを変更して再び初期化することはできません。"); } InstallDirectory = install_directory; SetDllDirectory(InstallDirectory); // AITalkを初期化する AitalkCore.Config config; config.VoiceDbSampleRate = VoiceSampleRate; config.VoiceDbDirectory = $"{InstallDirectory}\\Voice"; config.TimeoutMilliseconds = TimeoutMilliseconds; config.LicensePath = $"{InstallDirectory}\\aitalk.lic"; config.AuthenticateCodeSeed = authenticate_code; config.ReservedZero = 0; var result = AitalkCore.Result.Success; try { result = AitalkCore.Init(ref config); } catch (Exception e) { throw new AitalkException($"AITalkの初期化に失敗しました。", e); } if (result != AitalkCore.Result.Success) { throw new AitalkException($"AITalkの初期化に失敗しました。", result); } IsInitialized = true; }
/// <summary> /// 読み仮名を読み上げてWAVEファイルをストリームに出力する。 /// なお、ストリームへの書き込みは変換がすべて終わった後に行われる。 /// </summary> /// <param name="kana">読み仮名</param> /// <param name="wave_stream">WAVEファイルの出力先ストリーム</param> /// <param name="timeout">タイムアウト[ms]。0以下はタイムアウト無しで待ち続ける。</param> public static void KanaToSpeech(string kana, Stream wave_stream, int timeout = 0) { UpdateParameter(); // コールバックメソッドとの同期オブジェクトを用意する SpeechJobData job_data = new SpeechJobData(); job_data.BufferCapacity = 176400; job_data.Output = new List <byte>(); job_data.EventData = new List <TtsEventData>(); job_data.CloseEvent = new EventWaitHandle(false, EventResetMode.ManualReset); GCHandle gc_handle = GCHandle.Alloc(job_data); try { // 変換を開始する AitalkCore.JobParam job_param; job_param.ModeInOut = AitalkCore.JobInOut.KanaToWave; job_param.UserData = GCHandle.ToIntPtr(gc_handle); AitalkCore.Result result; result = AitalkCore.TextToSpeech(out int job_id, ref job_param, kana); if (result != AitalkCore.Result.Success) { throw new AitalkException("音声変換が開始できませんでした。", result); } // 変換の終了を待つ // timeoutで与えられた時間だけ待つ bool respond; respond = job_data.CloseEvent.WaitOne((0 < timeout) ? timeout : -1); // 変換を終了する result = AitalkCore.CloseSpeech(job_id); if (respond == false) { throw new AitalkException("音声変換がタイムアウトしました。"); } else if (result != AitalkCore.Result.Success) { throw new AitalkException("音声変換が正常に終了しませんでした。", result); } } finally { gc_handle.Free(); } // TTSイベントをJSONに変換する // 変換後の文字列にヌル終端がてら4の倍数の長さになるようパディングを施す MemoryStream event_stream = new MemoryStream(); var serializer = new DataContractJsonSerializer(typeof(List <TtsEventData>)); serializer.WriteObject(event_stream, job_data.EventData); int padding = 4 - ((int)event_stream.Length % 4); for (int cnt = 0; cnt < padding; cnt++) { event_stream.WriteByte(0x0); } byte[] event_json = event_stream.ToArray(); // データをWAVE形式で出力する // phonチャンクとしてTTSイベントを埋め込む byte[] data = job_data.Output.ToArray(); var writer = new BinaryWriter(wave_stream); writer.Write(new byte[4] { (byte)'R', (byte)'I', (byte)'F', (byte)'F' }); writer.Write(44 + event_json.Length + data.Length); writer.Write(new byte[4] { (byte)'W', (byte)'A', (byte)'V', (byte)'E' }); writer.Write(new byte[4] { (byte)'f', (byte)'m', (byte)'t', (byte)' ' }); writer.Write(16); writer.Write((short)0x1); writer.Write((short)1); writer.Write(VoiceSampleRate); writer.Write(2 * VoiceSampleRate); writer.Write((short)2); writer.Write((short)16); writer.Write(new byte[4] { (byte)'p', (byte)'h', (byte)'o', (byte)'n' }); writer.Write(event_json.Length); writer.Write(event_json); writer.Write(new byte[4] { (byte)'d', (byte)'a', (byte)'t', (byte)'a' }); writer.Write(data.Length); writer.Write(data); }