diff --git a/crates/voicevox_core/src/engine/model.rs b/crates/voicevox_core/src/engine/model.rs index d403d60ec..657c3c232 100644 --- a/crates/voicevox_core/src/engine/model.rs +++ b/crates/voicevox_core/src/engine/model.rs @@ -76,6 +76,12 @@ pub struct AudioQueryModel { kana: Option, } +impl AudioQueryModel { + pub(crate) fn with_kana(self, kana: Option) -> Self { + Self { kana, ..self } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/voicevox_core/src/voice_synthesizer.rs b/crates/voicevox_core/src/voice_synthesizer.rs index 59e219f48..604ee5ea7 100644 --- a/crates/voicevox_core/src/voice_synthesizer.rs +++ b/crates/voicevox_core/src/voice_synthesizer.rs @@ -25,36 +25,10 @@ impl From<&TtsOptions> for SynthesisOptions { } } -/// [`Synthesizer::create_accent_phrases`]のオプション。 -/// -/// [`Synthesizer::create_accent_phrases`]: Synthesizer::create_accent_phrases -#[derive(Default)] -pub struct AccentPhrasesOptions { - /// AquesTalk風記法としてテキストを解釈する。 - pub kana: bool, -} - -/// [`Synthesizer::audio_query`]のオプション。 -/// -/// [`Synthesizer::audio_query`]: Synthesizer::audio_query -#[derive(Default)] -pub struct AudioQueryOptions { - /// AquesTalk風記法としてテキストを解釈する。 - pub kana: bool, -} - -impl From<&TtsOptions> for AudioQueryOptions { - fn from(options: &TtsOptions) -> Self { - Self { kana: options.kana } - } -} - /// [`Synthesizer::tts`]のオプション。 /// /// [`Synthesizer::tts`]: Synthesizer::tts pub struct TtsOptions { - /// AquesTalk風記法としてテキストを解釈する。 - pub kana: bool, pub enable_interrogative_upspeak: bool, } @@ -68,7 +42,6 @@ impl Default for TtsOptions { fn default() -> Self { Self { enable_interrogative_upspeak: true, - kana: Default::default(), } } } @@ -267,12 +240,9 @@ impl Synthesizer { .await } - /// AccentPhrase (アクセント句)の配列を生成する。 - /// - /// `text`は[`options.kana`]が有効化されているときにはAquesTalk風記法として、そうでないときには - /// 日本語のテキストとして解釈される。 + /// AquesTalk風記法からAccentPhrase (アクセント句)の配列を生成する。 /// - /// # Examples + /// # Example /// #[cfg_attr(windows, doc = "```no_run")] // https://github.com/VOICEVOX/voicevox_core/issues/537 #[cfg_attr(not(windows), doc = "```")] @@ -308,16 +278,25 @@ impl Synthesizer { /// use voicevox_core::StyleId; /// /// let accent_phrases = syntesizer - /// .create_accent_phrases( - /// "こんにちは", // 日本語のテキスト - /// StyleId::new(302), - /// &Default::default(), - /// ) + /// .create_accent_phrases_from_kana("コンニチワ'", StyleId::new(302)) /// .await?; /// # /// # Ok(()) /// # } /// ``` + pub async fn create_accent_phrases_from_kana( + &self, + kana: &str, + style_id: StyleId, + ) -> Result> { + self.synthesis_engine + .replace_mora_data(&parse_kana(kana)?, style_id) + .await + } + + /// 日本語のテキストからAccentPhrase (アクセント句)の配列を生成する。 + /// + /// # Example /// #[cfg_attr(windows, doc = "```no_run")] // https://github.com/VOICEVOX/voicevox_core/issues/537 #[cfg_attr(not(windows), doc = "```")] @@ -350,39 +329,26 @@ impl Synthesizer { /// # syntesizer /// # }; /// # - /// use voicevox_core::{AccentPhrasesOptions, StyleId}; + /// use voicevox_core::StyleId; /// /// let accent_phrases = syntesizer - /// .create_accent_phrases( - /// "コンニチワ'", // AquesTalk風記法 - /// StyleId::new(302), - /// &AccentPhrasesOptions { kana: true }, - /// ) + /// .create_accent_phrases("こんにちは", StyleId::new(302)) /// .await?; /// # /// # Ok(()) /// # } /// ``` - /// - /// [`options.kana`]: crate::AccentPhrasesOptions::kana pub async fn create_accent_phrases( &self, text: &str, style_id: StyleId, - options: &AccentPhrasesOptions, ) -> Result> { if !self.synthesis_engine.is_openjtalk_dict_loaded() { return Err(ErrorRepr::NotLoadedOpenjtalkDict.into()); } - if options.kana { - self.synthesis_engine - .replace_mora_data(&parse_kana(text)?, style_id) - .await - } else { - self.synthesis_engine - .create_accent_phrases(text, style_id) - .await - } + self.synthesis_engine + .create_accent_phrases(text, style_id) + .await } /// AccentPhraseの配列の音高・音素長を、特定の声で生成しなおす。 @@ -418,12 +384,9 @@ impl Synthesizer { .await } - /// [AudioQuery]を生成する。 + /// AquesTalk風記法から[AudioQuery]を生成する。 /// - /// `text`は[`options.kana`]が有効化されているときにはAquesTalk風記法として、そうでないときには - /// 日本語のテキストとして解釈される。 - /// - /// # Examples + /// # Example /// #[cfg_attr(windows, doc = "```no_run")] // https://github.com/VOICEVOX/voicevox_core/issues/537 #[cfg_attr(not(windows), doc = "```")] @@ -459,17 +422,27 @@ impl Synthesizer { /// use voicevox_core::StyleId; /// /// let audio_query = syntesizer - /// .audio_query( - /// "こんにちは", // 日本語のテキスト - /// StyleId::new(302), - /// &Default::default(), - /// ) + /// .audio_query_from_kana("コンニチワ'", StyleId::new(302)) /// .await?; /// # /// # Ok(()) /// # } /// ``` /// + /// [AudioQuery]: crate::AudioQueryModel + pub async fn audio_query_from_kana( + &self, + kana: &str, + style_id: StyleId, + ) -> Result { + let accent_phrases = self.create_accent_phrases_from_kana(kana, style_id).await?; + Ok(AudioQueryModel::from_accent_phrases(accent_phrases).with_kana(Some(kana.to_owned()))) + } + + /// 日本語のテキストから[AudioQuery]を生成する。 + /// + /// # Examples + /// #[cfg_attr(windows, doc = "```no_run")] // https://github.com/VOICEVOX/voicevox_core/issues/537 #[cfg_attr(not(windows), doc = "```")] /// # #[tokio::main] @@ -501,14 +474,10 @@ impl Synthesizer { /// # syntesizer /// # }; /// # - /// use voicevox_core::{AudioQueryOptions, StyleId}; + /// use voicevox_core::StyleId; /// /// let audio_query = syntesizer - /// .audio_query( - /// "コンニチワ'", // AquesTalk風記法 - /// StyleId::new(302), - /// &AudioQueryOptions { kana: true }, - /// ) + /// .audio_query("こんにちは", StyleId::new(302)) /// .await?; /// # /// # Ok(()) @@ -516,46 +485,31 @@ impl Synthesizer { /// ``` /// /// [AudioQuery]: crate::AudioQueryModel - /// [`options.kana`]: crate::AudioQueryOptions::kana - pub async fn audio_query( + pub async fn audio_query(&self, text: &str, style_id: StyleId) -> Result { + let accent_phrases = self.create_accent_phrases(text, style_id).await?; + Ok(AudioQueryModel::from_accent_phrases(accent_phrases)) + } + + /// AquesTalk風記法から音声合成を行う。 + pub async fn tts_from_kana( &self, - text: &str, + kana: &str, style_id: StyleId, - options: &AudioQueryOptions, - ) -> Result { - let accent_phrases = self - .create_accent_phrases(text, style_id, &AccentPhrasesOptions { kana: options.kana }) - .await?; - let kana = create_kana(&accent_phrases); - Ok(AudioQueryModel::new( - accent_phrases, - 1., - 0., - 1., - 1., - 0.1, - 0.1, - SynthesisEngine::DEFAULT_SAMPLING_RATE, - false, - Some(kana), - )) + options: &TtsOptions, + ) -> Result> { + let audio_query = &self.audio_query_from_kana(kana, style_id).await?; + self.synthesis(audio_query, style_id, &SynthesisOptions::from(options)) + .await } - /// テキスト音声合成を行う。 - /// - /// `text`は[`options.kana`]が有効化されているときにはAquesTalk風記法として、そうでないときには - /// 日本語のテキストとして解釈される。 - /// - /// [`options.kana`]: crate::TtsOptions::kana + /// 日本語のテキストから音声合成を行う。 pub async fn tts( &self, text: &str, style_id: StyleId, options: &TtsOptions, ) -> Result> { - let audio_query = &self - .audio_query(text, style_id, &AudioQueryOptions::from(options)) - .await?; + let audio_query = &self.audio_query(text, style_id).await?; self.synthesis(audio_query, style_id, &SynthesisOptions::from(options)) .await } @@ -599,6 +553,24 @@ fn list_windows_video_cards() { } } +impl AudioQueryModel { + fn from_accent_phrases(accent_phrases: Vec) -> Self { + let kana = create_kana(&accent_phrases); + Self::new( + accent_phrases, + 1., + 0., + 1., + 1., + 0.1, + 0.1, + SynthesisEngine::DEFAULT_SAMPLING_RATE, + false, + Some(kana), + ) + } +} + #[cfg(test)] mod tests { @@ -832,21 +804,18 @@ mod tests { #[rstest] #[case( - "これはテストです", - false, + Input::Japanese("これはテストです"), TEXT_CONSONANT_VOWEL_DATA1, "コレワ'/テ'_ストデ_ス" )] #[case( - "コ'レワ/テ_スト'デ_ス", - true, + Input::Kana("コ'レワ/テ_スト'デ_ス"), TEXT_CONSONANT_VOWEL_DATA2, "コ'レワ/テ_スト'デ_ス" )] #[tokio::test] async fn audio_query_works( - #[case] input_text: &str, - #[case] input_kana_option: bool, + #[case] input: Input, #[case] expected_text_consonant_vowel_data: &TextConsonantVowelData, #[case] expected_kana_text: &str, ) { @@ -863,16 +832,15 @@ mod tests { let model = &VoiceModel::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); - let query = syntesizer - .audio_query( - input_text, - StyleId::new(0), - &AudioQueryOptions { - kana: input_kana_option, - }, - ) - .await - .unwrap(); + let query = match input { + Input::Kana(input) => { + syntesizer + .audio_query_from_kana(input, StyleId::new(0)) + .await + } + Input::Japanese(input) => syntesizer.audio_query(input, StyleId::new(0)).await, + } + .unwrap(); assert_eq!( query.accent_phrases().len(), @@ -913,12 +881,11 @@ mod tests { } #[rstest] - #[case("これはテストです", false, TEXT_CONSONANT_VOWEL_DATA1)] - #[case("コ'レワ/テ_スト'デ_ス", true, TEXT_CONSONANT_VOWEL_DATA2)] + #[case(Input::Japanese("これはテストです"), TEXT_CONSONANT_VOWEL_DATA1)] + #[case(Input::Kana("コ'レワ/テ_スト'デ_ス"), TEXT_CONSONANT_VOWEL_DATA2)] #[tokio::test] async fn accent_phrases_works( - #[case] input_text: &str, - #[case] input_kana_option: bool, + #[case] input: Input, #[case] expected_text_consonant_vowel_data: &TextConsonantVowelData, ) { let syntesizer = Synthesizer::new_with_initialize( @@ -934,16 +901,19 @@ mod tests { let model = &VoiceModel::sample().await.unwrap(); syntesizer.load_voice_model(model).await.unwrap(); - let accent_phrases = syntesizer - .create_accent_phrases( - input_text, - StyleId::new(0), - &AccentPhrasesOptions { - kana: input_kana_option, - }, - ) - .await - .unwrap(); + let accent_phrases = match input { + Input::Kana(input) => { + syntesizer + .create_accent_phrases_from_kana(input, StyleId::new(0)) + .await + } + Input::Japanese(input) => { + syntesizer + .create_accent_phrases(input, StyleId::new(0)) + .await + } + } + .unwrap(); assert_eq!( accent_phrases.len(), @@ -998,11 +968,7 @@ mod tests { syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = syntesizer - .create_accent_phrases( - "これはテストです", - StyleId::new(0), - &AccentPhrasesOptions { kana: false }, - ) + .create_accent_phrases("これはテストです", StyleId::new(0)) .await .unwrap(); @@ -1039,11 +1005,7 @@ mod tests { syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = syntesizer - .create_accent_phrases( - "これはテストです", - StyleId::new(0), - &AccentPhrasesOptions { kana: false }, - ) + .create_accent_phrases("これはテストです", StyleId::new(0)) .await .unwrap(); @@ -1076,11 +1038,7 @@ mod tests { syntesizer.load_voice_model(model).await.unwrap(); let accent_phrases = syntesizer - .create_accent_phrases( - "これはテストです", - StyleId::new(0), - &AccentPhrasesOptions { kana: false }, - ) + .create_accent_phrases("これはテストです", StyleId::new(0)) .await .unwrap(); @@ -1114,4 +1072,9 @@ mod tests { .flat_map(move |(before, after)| std::iter::zip(before.moras(), after.moras())) .any(|(before, after)| param(before) != param(after)) } + + enum Input { + Japanese(&'static str), + Kana(&'static str), + } } diff --git a/crates/voicevox_core_c_api/include/voicevox_core.h b/crates/voicevox_core_c_api/include/voicevox_core.h index 9e32bf982..d376544eb 100644 --- a/crates/voicevox_core_c_api/include/voicevox_core.h +++ b/crates/voicevox_core_c_api/include/voicevox_core.h @@ -279,16 +279,6 @@ typedef struct VoicevoxInitializeOptions { */ typedef const char *VoicevoxVoiceModelId; -/** - * ::voicevox_synthesizer_create_audio_query のオプション。 - */ -typedef struct VoicevoxAudioQueryOptions { - /** - * AquesTalk風記法としてテキストを解釈する - */ - bool kana; -} VoicevoxAudioQueryOptions; - /** * スタイルID。 * @@ -296,16 +286,6 @@ typedef struct VoicevoxAudioQueryOptions { */ typedef uint32_t VoicevoxStyleId; -/** - * ::voicevox_synthesizer_create_accent_phrases のオプション。 - */ -typedef struct VoicevoxAccentPhrasesOptions { - /** - * AquesTalk風記法としてテキストを解釈する - */ - bool kana; -} VoicevoxAccentPhrasesOptions; - /** * ::voicevox_synthesizer_synthesis のオプション。 */ @@ -320,10 +300,6 @@ typedef struct VoicevoxSynthesisOptions { * ::voicevox_synthesizer_tts のオプション。 */ typedef struct VoicevoxTtsOptions { - /** - * AquesTalk風記法としてテキストを解釈する - */ - bool kana; /** * 疑問文の調整を有効にする */ @@ -668,48 +644,61 @@ __declspec(dllimport) VoicevoxResultCode voicevox_create_supported_devices_json(char **output_supported_devices_json); /** - * デフォルトの AudioQuery のオプションを生成する - * @return デフォルト値が設定された AudioQuery オプション + * AquesTalk風記法から、AudioQueryをJSONとして生成する。 + * + * 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 + * + * @param [in] synthesizer 音声シンセサイザ + * @param [in] kana AquesTalk風記法 + * @param [in] style_id スタイルID + * @param [out] output_audio_query_json 生成先 + * + * @returns 結果コード + * + * \example{ + * ```c + * char *audio_query; + * voicevox_synthesizer_create_audio_query_from_kana(synthesizer, "コンニチワ'", + * 2, // "四国めたん (ノーマル)" + * &audio_query); + * ``` + * } + * + * \safety{ + * - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 + * - `output_audio_query_json`は書き込みについて有効でなければならない。 + * } */ #ifdef _WIN32 __declspec(dllimport) #endif -struct VoicevoxAudioQueryOptions voicevox_make_default_audio_query_options(void); +VoicevoxResultCode voicevox_synthesizer_create_audio_query_from_kana(const struct VoicevoxSynthesizer *synthesizer, + const char *kana, + VoicevoxStyleId style_id, + char **output_audio_query_json); /** - * AudioQueryをJSONとして生成する。 + * 日本語テキストから、AudioQueryをJSONとして生成する。 * * 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 * * @param [in] synthesizer 音声シンセサイザ - * @param [in] text UTF-8の日本語テキストまたはAquesTalk風記法 + * @param [in] text UTF-8の日本語テキスト * @param [in] style_id スタイルID - * @param [in] options オプション * @param [out] output_audio_query_json 生成先 * * @returns 結果コード * - * \examples{ - * ```c - * char *audio_query; - * voicevox_synthesizer_create_audio_query(synthesizer, - * "こんにちは", // 日本語テキスト - * 2, // "四国めたん (ノーマル)" - * (VoicevoxAudioQueryOptions){.kana = false}, - * &audio_query); - * ``` - * + * \example{ * ```c * char *audio_query; - * voicevox_synthesizer_create_audio_query(synthesizer, - * "コンニチワ'", // AquesTalk風記法 - * 2, // "四国めたん (ノーマル)" - * (VoicevoxAudioQueryOptions){.kana = true}, + * voicevox_synthesizer_create_audio_query(synthesizer, "こんにちは", + * 2, // "四国めたん (ノーマル)" * &audio_query); * ``` * } * - * * \safety{ * - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 * - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 @@ -722,48 +711,62 @@ __declspec(dllimport) VoicevoxResultCode voicevox_synthesizer_create_audio_query(const struct VoicevoxSynthesizer *synthesizer, const char *text, VoicevoxStyleId style_id, - struct VoicevoxAudioQueryOptions options, char **output_audio_query_json); /** - * デフォルトの `accent_phrases` のオプションを生成する - * @return デフォルト値が設定された `accent_phrases` のオプション + * AquesTalk風記法から、AccentPhrase (アクセント句)の配列をJSON形式で生成する。 + * + * 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 + * + * @param [in] synthesizer 音声シンセサイザ + * @param [in] kana AquesTalk風記法 + * @param [in] style_id スタイルID + * @param [out] output_accent_phrases_json 生成先 + * + * @returns 結果コード + * + * \example{ + * ```c + * char *accent_phrases; + * voicevox_synthesizer_create_accent_phrases_from_kana( + * synthesizer, "コンニチワ'", + * 2, // "四国めたん (ノーマル)" + * &accent_phrases); + * ``` + * } + * + * \safety{ + * - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 + * - `output_audio_query_json`は書き込みについて有効でなければならない。 + * } */ #ifdef _WIN32 __declspec(dllimport) #endif -struct VoicevoxAccentPhrasesOptions voicevox_make_default_accent_phrases_options(void); +VoicevoxResultCode voicevox_synthesizer_create_accent_phrases_from_kana(const struct VoicevoxSynthesizer *synthesizer, + const char *kana, + VoicevoxStyleId style_id, + char **output_accent_phrases_json); /** - * AccentPhrase (アクセント句)の配列をJSON形式で生成する。 + * 日本語テキストから、AccentPhrase (アクセント句)の配列をJSON形式で生成する。 * * 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 * * @param [in] synthesizer 音声シンセサイザ - * @param [in] text UTF-8の日本語テキストまたはAquesTalk風記法 + * @param [in] text UTF-8の日本語テキスト * @param [in] style_id スタイルID - * @param [in] options オプション * @param [out] output_accent_phrases_json 生成先 * * @returns 結果コード * - * \examples{ - * ```c - * char *accent_phrases; - * voicevox_synthesizer_create_accent_phrases( - * synthesizer, - * "こんにちは", // 日本語テキスト - * 2, // "四国めたん (ノーマル)" - * voicevox_default_accent_phrases_options, &accent_phrases); - * ``` - * + * \example{ * ```c * char *accent_phrases; - * voicevox_synthesizer_create_accent_phrases( - * synthesizer, - * "コンニチワ'", // AquesTalk風記法 - * 2, // "四国めたん (ノーマル)" - * (VoicevoxAccentPhrasesOptions){.kana = true}, &accent_phrases); + * voicevox_synthesizer_create_accent_phrases(synthesizer, "こんにちは", + * 2, // "四国めたん (ノーマル)" + * &accent_phrases); * ``` * } * @@ -779,7 +782,6 @@ __declspec(dllimport) VoicevoxResultCode voicevox_synthesizer_create_accent_phrases(const struct VoicevoxSynthesizer *synthesizer, const char *text, VoicevoxStyleId style_id, - struct VoicevoxAccentPhrasesOptions options, char **output_accent_phrases_json); /** @@ -910,12 +912,43 @@ __declspec(dllimport) struct VoicevoxTtsOptions voicevox_make_default_tts_options(void); /** - * テキスト音声合成を行う。 + * AquesTalk風記法から音声合成を行う。 + * + * 生成したWAVデータを解放するには ::voicevox_wav_free を使う。 + * + * @param [in] synthesizer + * @param [in] kana AquesTalk風記法 + * @param [in] style_id スタイルID + * @param [in] options オプション + * @param [out] output_wav_length 出力のバイト長 + * @param [out] output_wav 出力先 + * + * @returns 結果コード + * + * \safety{ + * - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 + * - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 + * - `output_wav_length`は書き込みについて有効でなければならない。 + * - `output_wav`は書き込みについて有効でなければならない。 + * } + */ +#ifdef _WIN32 +__declspec(dllimport) +#endif +VoicevoxResultCode voicevox_synthesizer_tts_from_kana(const struct VoicevoxSynthesizer *synthesizer, + const char *kana, + VoicevoxStyleId style_id, + struct VoicevoxTtsOptions options, + uintptr_t *output_wav_length, + uint8_t **output_wav); + +/** + * 日本語テキストから音声合成を行う。 * * 生成したWAVデータを解放するには ::voicevox_wav_free を使う。 * * @param [in] synthesizer - * @param [in] text UTF-8の日本語テキストまたはAquesTalk風記法 + * @param [in] text UTF-8の日本語テキスト * @param [in] style_id スタイルID * @param [in] options オプション * @param [out] output_wav_length 出力のバイト長 diff --git a/crates/voicevox_core_c_api/src/helpers.rs b/crates/voicevox_core_c_api/src/helpers.rs index 8b32607be..bd1c95c8f 100644 --- a/crates/voicevox_core_c_api/src/helpers.rs +++ b/crates/voicevox_core_c_api/src/helpers.rs @@ -85,28 +85,6 @@ pub(crate) fn ensure_utf8(s: &CStr) -> CApiResult<&str> { s.to_str().map_err(|_| CApiError::InvalidUtf8Input) } -impl From for VoicevoxAudioQueryOptions { - fn from(options: voicevox_core::AudioQueryOptions) -> Self { - Self { kana: options.kana } - } -} -impl From for voicevox_core::AudioQueryOptions { - fn from(options: VoicevoxAudioQueryOptions) -> Self { - Self { kana: options.kana } - } -} - -impl From for VoicevoxAccentPhrasesOptions { - fn from(options: voicevox_core::AccentPhrasesOptions) -> Self { - Self { kana: options.kana } - } -} -impl From for voicevox_core::AccentPhrasesOptions { - fn from(options: VoicevoxAccentPhrasesOptions) -> Self { - Self { kana: options.kana } - } -} - impl From for voicevox_core::SynthesisOptions { fn from(options: VoicevoxSynthesisOptions) -> Self { Self { @@ -159,7 +137,6 @@ impl From for voicevox_core::InitializeOptions { impl From for VoicevoxTtsOptions { fn from(options: voicevox_core::TtsOptions) -> Self { Self { - kana: options.kana, enable_interrogative_upspeak: options.enable_interrogative_upspeak, } } @@ -168,7 +145,6 @@ impl From for VoicevoxTtsOptions { impl From for voicevox_core::TtsOptions { fn from(options: VoicevoxTtsOptions) -> Self { Self { - kana: options.kana, enable_interrogative_upspeak: options.enable_interrogative_upspeak, } } diff --git a/crates/voicevox_core_c_api/src/lib.rs b/crates/voicevox_core_c_api/src/lib.rs index 4c692b85d..9bc6698b4 100644 --- a/crates/voicevox_core_c_api/src/lib.rs +++ b/crates/voicevox_core_c_api/src/lib.rs @@ -30,8 +30,8 @@ use tracing_subscriber::fmt::format::Writer; use tracing_subscriber::EnvFilter; use uuid::Uuid; use voicevox_core::{ - AccentPhraseModel, AudioQueryModel, AudioQueryOptions, OpenJtalk, TtsOptions, UserDictWord, - VoiceModel, VoiceModelId, + AccentPhraseModel, AudioQueryModel, OpenJtalk, TtsOptions, UserDictWord, VoiceModel, + VoiceModelId, }; use voicevox_core::{StyleId, SupportedDevices, SynthesisOptions, Synthesizer}; @@ -500,53 +500,75 @@ pub unsafe extern "C" fn voicevox_create_supported_devices_json( })()) } -/// ::voicevox_synthesizer_create_audio_query のオプション。 -#[repr(C)] -pub struct VoicevoxAudioQueryOptions { - /// AquesTalk風記法としてテキストを解釈する - kana: bool, -} - -/// デフォルトの AudioQuery のオプションを生成する -/// @return デフォルト値が設定された AudioQuery オプション -#[no_mangle] -pub extern "C" fn voicevox_make_default_audio_query_options() -> VoicevoxAudioQueryOptions { - voicevox_core::AudioQueryOptions::default().into() -} - -/// AudioQueryをJSONとして生成する。 +/// AquesTalk風記法から、AudioQueryをJSONとして生成する。 /// /// 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 /// /// @param [in] synthesizer 音声シンセサイザ -/// @param [in] text UTF-8の日本語テキストまたはAquesTalk風記法 +/// @param [in] kana AquesTalk風記法 /// @param [in] style_id スタイルID -/// @param [in] options オプション /// @param [out] output_audio_query_json 生成先 /// /// @returns 結果コード /// -/// \examples{ +/// \example{ /// ```c /// char *audio_query; -/// voicevox_synthesizer_create_audio_query(synthesizer, -/// "こんにちは", // 日本語テキスト -/// 2, // "四国めたん (ノーマル)" -/// (VoicevoxAudioQueryOptions){.kana = false}, -/// &audio_query); +/// voicevox_synthesizer_create_audio_query_from_kana(synthesizer, "コンニチワ'", +/// 2, // "四国めたん (ノーマル)" +/// &audio_query); /// ``` +/// } +/// +/// \safety{ +/// - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 +/// - `output_audio_query_json`は書き込みについて有効でなければならない。 +/// } +#[no_mangle] +pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query_from_kana( + synthesizer: &VoicevoxSynthesizer, + kana: *const c_char, + style_id: VoicevoxStyleId, + output_audio_query_json: NonNull<*mut c_char>, +) -> VoicevoxResultCode { + into_result_code_with_error((|| { + let kana = CStr::from_ptr(kana); + let kana = ensure_utf8(kana)?; + let audio_query = RUNTIME.block_on( + synthesizer + .synthesizer() + .audio_query_from_kana(kana, StyleId::new(style_id)), + )?; + let audio_query = CString::new(audio_query_model_to_json(&audio_query)) + .expect("should not contain '\\0'"); + output_audio_query_json + .as_ptr() + .write_unaligned(C_STRING_DROP_CHECKER.whitelist(audio_query).into_raw()); + Ok(()) + })()) +} + +/// 日本語テキストから、AudioQueryをJSONとして生成する。 +/// +/// 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 +/// +/// @param [in] synthesizer 音声シンセサイザ +/// @param [in] text UTF-8の日本語テキスト +/// @param [in] style_id スタイルID +/// @param [out] output_audio_query_json 生成先 +/// +/// @returns 結果コード /// +/// \example{ /// ```c /// char *audio_query; -/// voicevox_synthesizer_create_audio_query(synthesizer, -/// "コンニチワ'", // AquesTalk風記法 -/// 2, // "四国めたん (ノーマル)" -/// (VoicevoxAudioQueryOptions){.kana = true}, +/// voicevox_synthesizer_create_audio_query(synthesizer, "こんにちは", +/// 2, // "四国めたん (ノーマル)" /// &audio_query); /// ``` /// } /// -/// /// \safety{ /// - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 /// - `text`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 @@ -557,17 +579,16 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query( synthesizer: &VoicevoxSynthesizer, text: *const c_char, style_id: VoicevoxStyleId, - options: VoicevoxAudioQueryOptions, output_audio_query_json: NonNull<*mut c_char>, ) -> VoicevoxResultCode { into_result_code_with_error((|| { let text = CStr::from_ptr(text); - let japanese_or_kana = ensure_utf8(text)?; - let audio_query = RUNTIME.block_on(synthesizer.synthesizer().audio_query( - japanese_or_kana, - StyleId::new(style_id), - &AudioQueryOptions::from(options), - ))?; + let text = ensure_utf8(text)?; + let audio_query = RUNTIME.block_on( + synthesizer + .synthesizer() + .audio_query(text, StyleId::new(style_id)), + )?; let audio_query = CString::new(audio_query_model_to_json(&audio_query)) .expect("should not contain '\\0'"); output_audio_query_json @@ -577,49 +598,72 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_audio_query( })()) } -/// ::voicevox_synthesizer_create_accent_phrases のオプション。 -#[repr(C)] -pub struct VoicevoxAccentPhrasesOptions { - /// AquesTalk風記法としてテキストを解釈する - kana: bool, -} - -/// デフォルトの `accent_phrases` のオプションを生成する -/// @return デフォルト値が設定された `accent_phrases` のオプション -#[no_mangle] -pub extern "C" fn voicevox_make_default_accent_phrases_options() -> VoicevoxAccentPhrasesOptions { - voicevox_core::AccentPhrasesOptions::default().into() -} - -/// AccentPhrase (アクセント句)の配列をJSON形式で生成する。 +/// AquesTalk風記法から、AccentPhrase (アクセント句)の配列をJSON形式で生成する。 /// /// 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 /// /// @param [in] synthesizer 音声シンセサイザ -/// @param [in] text UTF-8の日本語テキストまたはAquesTalk風記法 +/// @param [in] kana AquesTalk風記法 /// @param [in] style_id スタイルID -/// @param [in] options オプション /// @param [out] output_accent_phrases_json 生成先 /// /// @returns 結果コード /// -/// \examples{ +/// \example{ /// ```c /// char *accent_phrases; -/// voicevox_synthesizer_create_accent_phrases( -/// synthesizer, -/// "こんにちは", // 日本語テキスト -/// 2, // "四国めたん (ノーマル)" -/// voicevox_default_accent_phrases_options, &accent_phrases); +/// voicevox_synthesizer_create_accent_phrases_from_kana( +/// synthesizer, "コンニチワ'", +/// 2, // "四国めたん (ノーマル)" +/// &accent_phrases); /// ``` +/// } /// +/// \safety{ +/// - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 +/// - `output_audio_query_json`は書き込みについて有効でなければならない。 +/// } +#[no_mangle] +pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases_from_kana( + synthesizer: &VoicevoxSynthesizer, + kana: *const c_char, + style_id: VoicevoxStyleId, + output_accent_phrases_json: NonNull<*mut c_char>, +) -> VoicevoxResultCode { + into_result_code_with_error((|| { + let kana = ensure_utf8(CStr::from_ptr(kana))?; + let accent_phrases = RUNTIME.block_on( + synthesizer + .synthesizer() + .create_accent_phrases_from_kana(kana, StyleId::new(style_id)), + )?; + let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) + .expect("should not contain '\\0'"); + output_accent_phrases_json + .as_ptr() + .write_unaligned(C_STRING_DROP_CHECKER.whitelist(accent_phrases).into_raw()); + Ok(()) + })()) +} + +/// 日本語テキストから、AccentPhrase (アクセント句)の配列をJSON形式で生成する。 +/// +/// 生成したJSON文字列を解放するには ::voicevox_json_free を使う。 +/// +/// @param [in] synthesizer 音声シンセサイザ +/// @param [in] text UTF-8の日本語テキスト +/// @param [in] style_id スタイルID +/// @param [out] output_accent_phrases_json 生成先 +/// +/// @returns 結果コード +/// +/// \example{ /// ```c /// char *accent_phrases; -/// voicevox_synthesizer_create_accent_phrases( -/// synthesizer, -/// "コンニチワ'", // AquesTalk風記法 -/// 2, // "四国めたん (ノーマル)" -/// (VoicevoxAccentPhrasesOptions){.kana = true}, &accent_phrases); +/// voicevox_synthesizer_create_accent_phrases(synthesizer, "こんにちは", +/// 2, // "四国めたん (ノーマル)" +/// &accent_phrases); /// ``` /// } /// @@ -633,16 +677,15 @@ pub unsafe extern "C" fn voicevox_synthesizer_create_accent_phrases( synthesizer: &VoicevoxSynthesizer, text: *const c_char, style_id: VoicevoxStyleId, - options: VoicevoxAccentPhrasesOptions, output_accent_phrases_json: NonNull<*mut c_char>, ) -> VoicevoxResultCode { into_result_code_with_error((|| { let text = ensure_utf8(CStr::from_ptr(text))?; - let accent_phrases = RUNTIME.block_on(synthesizer.synthesizer().create_accent_phrases( - text, - StyleId::new(style_id), - &options.into(), - ))?; + let accent_phrases = RUNTIME.block_on( + synthesizer + .synthesizer() + .create_accent_phrases(text, StyleId::new(style_id)), + )?; let accent_phrases = CString::new(accent_phrases_to_json(&accent_phrases)) .expect("should not contain '\\0'"); output_accent_phrases_json @@ -836,8 +879,6 @@ pub unsafe extern "C" fn voicevox_synthesizer_synthesis( /// ::voicevox_synthesizer_tts のオプション。 #[repr(C)] pub struct VoicevoxTtsOptions { - /// AquesTalk風記法としてテキストを解釈する - kana: bool, /// 疑問文の調整を有効にする enable_interrogative_upspeak: bool, } @@ -849,12 +890,52 @@ pub extern "C" fn voicevox_make_default_tts_options() -> VoicevoxTtsOptions { voicevox_core::TtsOptions::default().into() } -/// テキスト音声合成を行う。 +/// AquesTalk風記法から音声合成を行う。 +/// +/// 生成したWAVデータを解放するには ::voicevox_wav_free を使う。 +/// +/// @param [in] synthesizer +/// @param [in] kana AquesTalk風記法 +/// @param [in] style_id スタイルID +/// @param [in] options オプション +/// @param [out] output_wav_length 出力のバイト長 +/// @param [out] output_wav 出力先 +/// +/// @returns 結果コード +/// +/// \safety{ +/// - `synthesizer`は ::voicevox_synthesizer_new_with_initialize で得たものでなければならず、また ::voicevox_synthesizer_delete で解放されていてはいけない。 +/// - `kana`はヌル終端文字列を指し、かつ読み込みについて有効でなければならない。 +/// - `output_wav_length`は書き込みについて有効でなければならない。 +/// - `output_wav`は書き込みについて有効でなければならない。 +/// } +#[no_mangle] +pub unsafe extern "C" fn voicevox_synthesizer_tts_from_kana( + synthesizer: &VoicevoxSynthesizer, + kana: *const c_char, + style_id: VoicevoxStyleId, + options: VoicevoxTtsOptions, + output_wav_length: NonNull, + output_wav: NonNull<*mut u8>, +) -> VoicevoxResultCode { + into_result_code_with_error((|| { + let kana = ensure_utf8(CStr::from_ptr(kana))?; + let output = RUNTIME.block_on(synthesizer.synthesizer().tts_from_kana( + kana, + StyleId::new(style_id), + &TtsOptions::from(options), + ))?; + U8_SLICE_OWNER.own_and_lend(output, output_wav, output_wav_length); + Ok(()) + })()) +} + +/// 日本語テキストから音声合成を行う。 /// /// 生成したWAVデータを解放するには ::voicevox_wav_free を使う。 /// /// @param [in] synthesizer -/// @param [in] text UTF-8の日本語テキストまたはAquesTalk風記法 +/// @param [in] text UTF-8の日本語テキスト /// @param [in] style_id スタイルID /// @param [in] options オプション /// @param [out] output_wav_length 出力のバイト長 diff --git a/crates/voicevox_core_c_api/tests/e2e/symbols.rs b/crates/voicevox_core_c_api/tests/e2e/symbols.rs index afcb105b2..ae929ad0f 100644 --- a/crates/voicevox_core_c_api/tests/e2e/symbols.rs +++ b/crates/voicevox_core_c_api/tests/e2e/symbols.rs @@ -59,15 +59,21 @@ pub(crate) struct Symbols<'lib> { Symbol<'lib, unsafe extern "C" fn(*const VoicevoxSynthesizer) -> *mut c_char>, pub(crate) voicevox_create_supported_devices_json: Symbol<'lib, unsafe extern "C" fn(*mut *mut c_char) -> VoicevoxResultCode>, - pub(crate) voicevox_make_default_audio_query_options: - Symbol<'lib, unsafe extern "C" fn() -> VoicevoxAudioQueryOptions>, + pub(crate) voicevox_synthesizer_create_audio_query_from_kana: Symbol< + 'lib, + unsafe extern "C" fn( + *const VoicevoxSynthesizer, + *const c_char, + VoicevoxStyleId, + *mut *mut c_char, + ) -> VoicevoxResultCode, + >, pub(crate) voicevox_synthesizer_create_audio_query: Symbol< 'lib, unsafe extern "C" fn( *const VoicevoxSynthesizer, *const c_char, VoicevoxStyleId, - VoicevoxAudioQueryOptions, *mut *mut c_char, ) -> VoicevoxResultCode, >, @@ -86,6 +92,17 @@ pub(crate) struct Symbols<'lib> { >, pub(crate) voicevox_make_default_tts_options: Symbol<'lib, unsafe extern "C" fn() -> VoicevoxTtsOptions>, + pub(crate) voicevox_synthesizer_tts_from_kana: Symbol< + 'lib, + unsafe extern "C" fn( + *const VoicevoxSynthesizer, + *const c_char, + VoicevoxStyleId, + VoicevoxTtsOptions, + *mut usize, + *mut *mut u8, + ) -> VoicevoxResultCode, + >, pub(crate) voicevox_synthesizer_tts: Symbol< 'lib, unsafe extern "C" fn( @@ -205,11 +222,12 @@ impl<'lib> Symbols<'lib> { voicevox_synthesizer_is_loaded_voice_model, voicevox_synthesizer_create_metas_json, voicevox_create_supported_devices_json, - voicevox_make_default_audio_query_options, + voicevox_synthesizer_create_audio_query_from_kana, voicevox_synthesizer_create_audio_query, voicevox_make_default_synthesis_options, voicevox_synthesizer_synthesis, voicevox_make_default_tts_options, + voicevox_synthesizer_tts_from_kana, voicevox_synthesizer_tts, voicevox_json_free, voicevox_wav_free, @@ -286,11 +304,6 @@ pub(crate) struct VoicevoxInitializeOptions { pub(crate) _cpu_num_threads: u16, } -#[repr(C)] -pub(crate) struct VoicevoxAudioQueryOptions { - _kana: bool, -} - #[repr(C)] pub(crate) struct VoicevoxSynthesisOptions { _enable_interrogative_upspeak: bool, @@ -298,7 +311,6 @@ pub(crate) struct VoicevoxSynthesisOptions { #[repr(C)] pub(crate) struct VoicevoxTtsOptions { - _kana: bool, _enable_interrogative_upspeak: bool, } diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/tts_via_audio_query.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/tts_via_audio_query.rs index 0ff0ef6e9..83a9d7bcf 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases/tts_via_audio_query.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/tts_via_audio_query.rs @@ -34,7 +34,6 @@ impl assert_cdylib::TestCase for TestCase { voicevox_synthesizer_new_with_initialize, voicevox_synthesizer_delete, voicevox_synthesizer_load_voice_model, - voicevox_make_default_audio_query_options, voicevox_synthesizer_create_audio_query, voicevox_make_default_synthesis_options, voicevox_synthesizer_synthesis, @@ -84,7 +83,6 @@ impl assert_cdylib::TestCase for TestCase { synthesizer, text.as_ptr(), STYLE_ID, - voicevox_make_default_audio_query_options(), audio_query.as_mut_ptr(), )); audio_query.assume_init() diff --git a/crates/voicevox_core_c_api/tests/e2e/testcases/user_dict_load.rs b/crates/voicevox_core_c_api/tests/e2e/testcases/user_dict_load.rs index b6f69e7a1..85cd40dc2 100644 --- a/crates/voicevox_core_c_api/tests/e2e/testcases/user_dict_load.rs +++ b/crates/voicevox_core_c_api/tests/e2e/testcases/user_dict_load.rs @@ -32,7 +32,6 @@ impl assert_cdylib::TestCase for TestCase { voicevox_user_dict_add_word, voicevox_user_dict_delete, voicevox_make_default_initialize_options, - voicevox_make_default_audio_query_options, voicevox_open_jtalk_rc_new, voicevox_open_jtalk_rc_use_user_dict, voicevox_open_jtalk_rc_delete, @@ -101,7 +100,6 @@ impl assert_cdylib::TestCase for TestCase { synthesizer, cstr!("this_word_should_not_exist_in_default_dictionary").as_ptr(), STYLE_ID, - voicevox_make_default_audio_query_options(), &mut audio_query_without_dict, )); let audio_query_without_dict = serde_json::from_str::( @@ -115,7 +113,6 @@ impl assert_cdylib::TestCase for TestCase { synthesizer, cstr!("this_word_should_not_exist_in_default_dictionary").as_ptr(), STYLE_ID, - voicevox_make_default_audio_query_options(), &mut audio_query_with_dict, )); diff --git a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java index 5739b14f7..5f3df9ea8 100644 --- a/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java +++ b/crates/voicevox_core_java_api/lib/src/main/java/jp/hiroshiba/voicevoxcore/Synthesizer.java @@ -52,29 +52,83 @@ public boolean isLoadedVoiceModel(String voiceModelId) { } /** - * {@link AudioQuery} を生成するためのオブジェクトを生成する。 + * AquesTalk風記法から {@link AudioQuery} を生成する。 * - * @param text テキスト。 + * @param kana AquesTalk風記法。 * @param styleId スタイルID。 - * @return {@link CreateAudioQueryConfigurator}。 - * @see CreateAudioQueryConfigurator#execute + * @return {@link AudioQuery}。 */ @Nonnull - public CreateAudioQueryConfigurator createAudioQuery(String text, int styleId) { - return new CreateAudioQueryConfigurator(this, text, styleId); + public AudioQuery createAudioQueryFromKana(String kana, int styleId) { + if (!Utils.isU32(styleId)) { + throw new IllegalArgumentException("styleId"); + } + String queryJson = rsAudioQueryFromKana(kana, styleId); + Gson gson = new Gson(); + + AudioQuery audioQuery = gson.fromJson(queryJson, AudioQuery.class); + if (audioQuery == null) { + throw new NullPointerException("audio_query"); + } + return audioQuery; + } + + /** + * 日本語のテキストから {@link AudioQuery} を生成する。 + * + * @param text 日本語のテキスト。 + * @param styleId スタイルID。 + * @return {@link AudioQuery}。 + */ + @Nonnull + public AudioQuery createAudioQuery(String text, int styleId) { + if (!Utils.isU32(styleId)) { + throw new IllegalArgumentException("styleId"); + } + String queryJson = rsAudioQuery(text, styleId); + Gson gson = new Gson(); + + AudioQuery audioQuery = gson.fromJson(queryJson, AudioQuery.class); + if (audioQuery == null) { + throw new NullPointerException("audio_query"); + } + return audioQuery; + } + + /** + * AquesTalk風記法から {@link AccentPhrase} のリストを生成する。 + * + * @param kana AquesTalk風記法。 + * @param styleId スタイルID。 + * @return {@link AccentPhrase} のリスト。 + */ + @Nonnull + public List createAccentPhrasesFromKana(String kana, int styleId) { + String accentPhrasesJson = rsAccentPhrasesFromKana(kana, styleId); + Gson gson = new Gson(); + AccentPhrase[] rawAccentPhrases = gson.fromJson(accentPhrasesJson, AccentPhrase[].class); + if (rawAccentPhrases == null) { + throw new NullPointerException("accent_phrases"); + } + return new ArrayList(Arrays.asList(rawAccentPhrases)); } /** - * {@link AccentPhrase} のリストを生成するためのオブジェクトを生成する。 + * 日本語のテキストから {@link AccentPhrase} のリストを生成する。 * - * @param text テキスト。 + * @param text 日本語のテキスト。 * @param styleId スタイルID。 - * @return {@link CreateAccentPhrasesConfigurator}。 - * @see CreateAccentPhrasesConfigurator#execute + * @return {@link AccentPhrase} のリスト。 */ @Nonnull - public CreateAccentPhrasesConfigurator createAccentPhrases(String text, int styleId) { - return new CreateAccentPhrasesConfigurator(this, text, styleId); + public List createAccentPhrases(String text, int styleId) { + String accentPhrasesJson = rsAccentPhrases(text, styleId); + Gson gson = new Gson(); + AccentPhrase[] rawAccentPhrases = gson.fromJson(accentPhrasesJson, AccentPhrase[].class); + if (rawAccentPhrases == null) { + throw new NullPointerException("accent_phrases"); + } + return new ArrayList(Arrays.asList(rawAccentPhrases)); } /** @@ -145,9 +199,22 @@ public SynthesisConfigurator synthesis(AudioQuery audioQuery, int styleId) { } /** - * テキスト音声合成を実行するためのオブジェクトを生成する。 + * AquesTalk風記法をもとに音声合成を実行するためのオブジェクトを生成する。 * - * @param text テキスト。 + * @param kana AquesTalk風記法。 + * @param styleId スタイルID。 + * @return {@link TtsFromKanaConfigurator}。 + * @see TtsFromKanaConfigurator#execute + */ + @Nonnull + public TtsFromKanaConfigurator ttsFromKana(String kana, int styleId) { + return new TtsFromKanaConfigurator(this, kana, styleId); + } + + /** + * 日本語のテキストをもとに音声合成を実行するためのオブジェクトを生成する。 + * + * @param text 日本語のテキスト。 * @param styleId スタイルID。 * @return {@link TtsConfigurator}。 * @see TtsConfigurator#execute @@ -166,10 +233,16 @@ public TtsConfigurator tts(String text, int styleId) { private native boolean rsIsLoadedVoiceModel(String voiceModelId); @Nonnull - private native String rsAudioQuery(String text, int styleId, boolean kana); + private native String rsAudioQueryFromKana(String kana, int styleId); + + @Nonnull + private native String rsAudioQuery(String text, int styleId); @Nonnull - private native String rsAccentPhrases(String text, int styleId, boolean kana); + private native String rsAccentPhrasesFromKana(String kana, int styleId); + + @Nonnull + private native String rsAccentPhrases(String text, int styleId); @Nonnull private native String rsReplaceMoraData(String accentPhrasesJson, int styleId, boolean kana); @@ -185,8 +258,10 @@ private native byte[] rsSynthesis( String queryJson, int styleId, boolean enableInterrogativeUpspeak); @Nonnull - private native byte[] rsTts( - String text, int styleId, boolean kana, boolean enableInterrogativeUpspeak); + private native byte[] rsTtsFromKana(String kana, int styleId, boolean enableInterrogativeUpspeak); + + @Nonnull + private native byte[] rsTts(String text, int styleId, boolean enableInterrogativeUpspeak); private native void rsDrop(); @@ -258,117 +333,65 @@ public static enum AccelerationMode { GPU, } - /** {@link Synthesizer#createAudioQuery} のオプション。 */ - public class CreateAudioQueryConfigurator { + /** {@link Synthesizer#synthesis} のオプション。 */ + public class SynthesisConfigurator { private Synthesizer synthesizer; - private String text; + private AudioQuery audioQuery; private int styleId; - private boolean kana; + private boolean interrogativeUpspeak; - private CreateAudioQueryConfigurator(Synthesizer synthesizer, String text, int styleId) { + private SynthesisConfigurator(Synthesizer synthesizer, AudioQuery audioQuery, int styleId) { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } this.synthesizer = synthesizer; - this.text = text; + this.audioQuery = audioQuery; this.styleId = styleId; - this.kana = false; + this.interrogativeUpspeak = false; } /** - * 入力テキストをAquesTalk風記法として解釈するかどうか。 + * 疑問文の調整を有効にするかどうか。 * - * @param kana 入力テキストをAquesTalk風記法として解釈するかどうか。 - * @return {@link CreateAudioQueryConfigurator}。 + * @param interrogativeUpspeak 疑問文の調整を有効にするかどうか。 + * @return {@link SynthesisConfigurator}。 */ @Nonnull - public CreateAudioQueryConfigurator kana(boolean kana) { - this.kana = kana; + public SynthesisConfigurator interrogativeUpspeak(boolean interrogativeUpspeak) { + this.interrogativeUpspeak = interrogativeUpspeak; return this; } /** - * {@link AudioQuery} を生成する。 + * {@link AudioQuery} から音声合成する。 * - * @return {@link AudioQuery}。 + * @return 音声データ。 */ @Nonnull - public AudioQuery execute() { - if (!Utils.isU32(styleId)) { - throw new IllegalArgumentException("styleId"); - } - String queryJson = synthesizer.rsAudioQuery(this.text, this.styleId, this.kana); - Gson gson = new Gson(); - - AudioQuery audioQuery = gson.fromJson(queryJson, AudioQuery.class); - if (audioQuery == null) { - throw new NullPointerException("audio_query"); - } - return audioQuery; - } - } - - /** {@link Synthesizer#createAccentPhrases} のオプション。 */ - public class CreateAccentPhrasesConfigurator { - private Synthesizer synthesizer; - private String text; - private int styleId; - private boolean kana; - - private CreateAccentPhrasesConfigurator(Synthesizer synthesizer, String text, int styleId) { + public byte[] execute() { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } - this.synthesizer = synthesizer; - this.text = text; - this.styleId = styleId; - this.kana = false; - } - - /** - * 入力テキストをAquesTalk風記法として解釈するかどうか。 - * - * @param kana 入力テキストをAquesTalk風記法として解釈するかどうか。 - * @return {@link CreateAudioQueryConfigurator}。 - */ - @Nonnull - public CreateAccentPhrasesConfigurator kana(boolean kana) { - this.kana = kana; - return this; - } - - /** - * {@link AccentPhrase} のリストを取得する。 - * - * @return {@link AccentPhrase} のリスト。 - */ - @Nonnull - public List execute() { - String accentPhrasesJson = synthesizer.rsAccentPhrases(this.text, this.styleId, this.kana); Gson gson = new Gson(); - AccentPhrase[] rawAccentPhrases = gson.fromJson(accentPhrasesJson, AccentPhrase[].class); - if (rawAccentPhrases == null) { - throw new NullPointerException("accent_phrases"); - } - return new ArrayList(Arrays.asList(rawAccentPhrases)); + String queryJson = gson.toJson(this.audioQuery); + return synthesizer.rsSynthesis(queryJson, this.styleId, this.interrogativeUpspeak); } } - /** {@link Synthesizer#synthesis} のオプション。 */ - public class SynthesisConfigurator { + /** {@link Synthesizer#ttsFromKana} のオプション。 */ + public class TtsFromKanaConfigurator { private Synthesizer synthesizer; - private AudioQuery audioQuery; + private String kana; private int styleId; private boolean interrogativeUpspeak; - private SynthesisConfigurator(Synthesizer synthesizer, AudioQuery audioQuery, int styleId) { + private TtsFromKanaConfigurator(Synthesizer synthesizer, String kana, int styleId) { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } this.synthesizer = synthesizer; - this.audioQuery = audioQuery; + this.kana = kana; this.styleId = styleId; - this.interrogativeUpspeak = false; } /** @@ -378,7 +401,7 @@ private SynthesisConfigurator(Synthesizer synthesizer, AudioQuery audioQuery, in * @return {@link SynthesisConfigurator}。 */ @Nonnull - public SynthesisConfigurator interrogativeUpspeak(boolean interrogativeUpspeak) { + public TtsFromKanaConfigurator interrogativeUpspeak(boolean interrogativeUpspeak) { this.interrogativeUpspeak = interrogativeUpspeak; return this; } @@ -393,9 +416,7 @@ public byte[] execute() { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } - Gson gson = new Gson(); - String queryJson = gson.toJson(this.audioQuery); - return synthesizer.rsSynthesis(queryJson, this.styleId, this.interrogativeUpspeak); + return synthesizer.rsTtsFromKana(this.kana, this.styleId, this.interrogativeUpspeak); } } @@ -404,7 +425,6 @@ public class TtsConfigurator { private Synthesizer synthesizer; private String text; private int styleId; - private boolean kana; private boolean interrogativeUpspeak; private TtsConfigurator(Synthesizer synthesizer, String text, int styleId) { @@ -414,19 +434,6 @@ private TtsConfigurator(Synthesizer synthesizer, String text, int styleId) { this.synthesizer = synthesizer; this.text = text; this.styleId = styleId; - this.kana = false; - } - - /** - * 入力テキストをAquesTalk風記法として解釈するかどうか。 - * - * @param kana 入力テキストをAquesTalk風記法として解釈するかどうか。 - * @return {@link CreateAudioQueryConfigurator}。 - */ - @Nonnull - public TtsConfigurator kana(boolean kana) { - this.kana = kana; - return this; } /** @@ -451,7 +458,7 @@ public byte[] execute() { if (!Utils.isU32(styleId)) { throw new IllegalArgumentException("styleId"); } - return synthesizer.rsTts(this.text, this.styleId, this.kana, this.interrogativeUpspeak); + return synthesizer.rsTts(this.text, this.styleId, this.interrogativeUpspeak); } } } diff --git a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/SynthesizerTest.java b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/SynthesizerTest.java index fe9b8220b..f5cdaea66 100644 --- a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/SynthesizerTest.java +++ b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/SynthesizerTest.java @@ -50,7 +50,7 @@ void checkAudioQuery() { OpenJtalk openJtalk = loadOpenJtalk(); Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); synthesizer.loadVoiceModel(model); - AudioQuery query = synthesizer.createAudioQuery("こんにちは", model.metas[0].styles[0].id).execute(); + AudioQuery query = synthesizer.createAudioQuery("こんにちは", model.metas[0].styles[0].id); synthesizer.synthesis(query, model.metas[0].styles[0].id).execute(); } @@ -62,7 +62,7 @@ void checkAccentPhrases() { Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); synthesizer.loadVoiceModel(model); List accentPhrases = - synthesizer.createAccentPhrases("こんにちは", model.metas[0].styles[0].id).execute(); + synthesizer.createAccentPhrases("こんにちは", model.metas[0].styles[0].id); List accentPhrases2 = synthesizer.replaceMoraPitch(accentPhrases, model.metas[1].styles[0].id); assertTrue( @@ -91,6 +91,6 @@ void checkTts() { OpenJtalk openJtalk = loadOpenJtalk(); Synthesizer synthesizer = Synthesizer.builder(openJtalk).build(); synthesizer.loadVoiceModel(model); - synthesizer.tts("こんにちは", model.metas[0].styles[0].id).execute(); + synthesizer.tts("こんにちは", model.metas[0].styles[0].id); } } diff --git a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/UserDictTest.java b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/UserDictTest.java index a7847efba..9eb1077f5 100644 --- a/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/UserDictTest.java +++ b/crates/voicevox_core_java_api/lib/src/test/java/jp/hiroshiba/voicevoxcore/UserDictTest.java @@ -18,18 +18,14 @@ void checkLoad() { UserDict userDict = new UserDict(); synthesizer.loadVoiceModel(model); AudioQuery query1 = - synthesizer - .createAudioQuery( - "this_word_should_not_exist_in_default_dictionary", model.metas[0].styles[0].id) - .execute(); + synthesizer.createAudioQuery( + "this_word_should_not_exist_in_default_dictionary", model.metas[0].styles[0].id); userDict.addWord(new UserDict.Word("this_word_should_not_exist_in_default_dictionary", "テスト")); openJtalk.useUserDict(userDict); AudioQuery query2 = - synthesizer - .createAudioQuery( - "this_word_should_not_exist_in_default_dictionary", model.metas[0].styles[0].id) - .execute(); + synthesizer.createAudioQuery( + "this_word_should_not_exist_in_default_dictionary", model.metas[0].styles[0].id); assertTrue(query1.kana != query2.kana); } diff --git a/crates/voicevox_core_java_api/src/synthesizer.rs b/crates/voicevox_core_java_api/src/synthesizer.rs index ed217462c..9f245f8f7 100644 --- a/crates/voicevox_core_java_api/src/synthesizer.rs +++ b/crates/voicevox_core_java_api/src/synthesizer.rs @@ -127,13 +127,44 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsIsLoadedV .into() } +#[no_mangle] +unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAudioQueryFromKana< + 'local, +>( + env: JNIEnv<'local>, + this: JObject<'local>, + kana: JString<'local>, + style_id: jint, +) -> jobject { + throw_if_err(env, std::ptr::null_mut(), |env| { + let kana: String = env.get_string(&kana)?.into(); + let style_id = style_id as u32; + + let internal = env + .get_rust_field::<_, _, Arc>>(&this, "handle")? + .clone(); + + let audio_query = { + let internal = internal.lock().unwrap(); + RUNTIME.block_on( + internal.audio_query_from_kana(&kana, voicevox_core::StyleId::new(style_id)), + )? + }; + + let query_json = serde_json::to_string(&audio_query)?; + + let j_audio_query = env.new_string(query_json)?; + + Ok(j_audio_query.into_raw()) + }) +} + #[no_mangle] unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAudioQuery<'local>( env: JNIEnv<'local>, this: JObject<'local>, text: JString<'local>, style_id: jint, - kana: jboolean, ) -> jobject { throw_if_err(env, std::ptr::null_mut(), |env| { let text: String = env.get_string(&text)?.into(); @@ -145,15 +176,7 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAudioQuer let audio_query = { let internal = internal.lock().unwrap(); - let options = voicevox_core::AudioQueryOptions { - kana: kana != 0, - // ..Default::default() - }; - RUNTIME.block_on(internal.audio_query( - &text, - voicevox_core::StyleId::new(style_id), - &options, - ))? + RUNTIME.block_on(internal.audio_query(&text, voicevox_core::StyleId::new(style_id)))? }; let query_json = serde_json::to_string(&audio_query)?; @@ -164,13 +187,45 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAudioQuer }) } +#[no_mangle] +unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAccentPhrasesFromKana< + 'local, +>( + env: JNIEnv<'local>, + this: JObject<'local>, + kana: JString<'local>, + style_id: jint, +) -> jobject { + throw_if_err(env, std::ptr::null_mut(), |env| { + let kana: String = env.get_string(&kana)?.into(); + let style_id = style_id as u32; + + let internal = env + .get_rust_field::<_, _, Arc>>(&this, "handle")? + .clone(); + + let accent_phrases = { + let internal = internal.lock().unwrap(); + RUNTIME.block_on( + internal + .create_accent_phrases_from_kana(&kana, voicevox_core::StyleId::new(style_id)), + )? + }; + + let query_json = serde_json::to_string(&accent_phrases)?; + + let j_accent_phrases = env.new_string(query_json)?; + + Ok(j_accent_phrases.into_raw()) + }) +} + #[no_mangle] unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAccentPhrases<'local>( env: JNIEnv<'local>, this: JObject<'local>, text: JString<'local>, style_id: jint, - kana: jboolean, ) -> jobject { throw_if_err(env, std::ptr::null_mut(), |env| { let text: String = env.get_string(&text)?.into(); @@ -182,15 +237,9 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsAccentPhr let accent_phrases = { let internal = internal.lock().unwrap(); - let options = voicevox_core::AccentPhrasesOptions { - kana: kana != 0, - // ..Default::default() - }; - RUNTIME.block_on(internal.create_accent_phrases( - &text, - voicevox_core::StyleId::new(style_id), - &options, - ))? + RUNTIME.block_on( + internal.create_accent_phrases(&text, voicevox_core::StyleId::new(style_id)), + )? }; let query_json = serde_json::to_string(&accent_phrases)?; @@ -330,13 +379,47 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsSynthesis }) } +#[no_mangle] +unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsTtsFromKana<'local>( + env: JNIEnv<'local>, + this: JObject<'local>, + kana: JString<'local>, + style_id: jint, + enable_interrogative_upspeak: jboolean, +) -> jobject { + throw_if_err(env, std::ptr::null_mut(), |env| { + let kana: String = env.get_string(&kana)?.into(); + let style_id = style_id as u32; + + let internal = env + .get_rust_field::<_, _, Arc>>(&this, "handle")? + .clone(); + + let wave = { + let internal = internal.lock().unwrap(); + let options = voicevox_core::TtsOptions { + enable_interrogative_upspeak: enable_interrogative_upspeak != 0, + // ..Default::default() + }; + RUNTIME.block_on(internal.tts_from_kana( + &kana, + voicevox_core::StyleId::new(style_id), + &options, + ))? + }; + + let j_bytes = env.byte_array_from_slice(&wave)?; + + Ok(j_bytes.into_raw()) + }) +} + #[no_mangle] unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsTts<'local>( env: JNIEnv<'local>, this: JObject<'local>, query_json: JString<'local>, style_id: jint, - kana: jboolean, enable_interrogative_upspeak: jboolean, ) -> jobject { throw_if_err(env, std::ptr::null_mut(), |env| { @@ -350,7 +433,6 @@ unsafe extern "system" fn Java_jp_hiroshiba_voicevoxcore_Synthesizer_rsTts<'loca let wave = { let internal = internal.lock().unwrap(); let options = voicevox_core::TtsOptions { - kana: kana != 0, enable_interrogative_upspeak: enable_interrogative_upspeak != 0, // ..Default::default() }; diff --git a/crates/voicevox_core_python_api/python/test/test_user_dict_load.py b/crates/voicevox_core_python_api/python/test/test_user_dict_load.py index d9829e6da..667dfe7f3 100644 --- a/crates/voicevox_core_python_api/python/test/test_user_dict_load.py +++ b/crates/voicevox_core_python_api/python/test/test_user_dict_load.py @@ -19,7 +19,7 @@ async def test_user_dict_load() -> None: await synthesizer.load_voice_model(model) audio_query_without_dict = await synthesizer.audio_query( - "this_word_should_not_exist_in_default_dictionary", style_id=0, kana=False + "this_word_should_not_exist_in_default_dictionary", style_id=0 ) temp_dict = voicevox_core.UserDict() @@ -34,6 +34,6 @@ async def test_user_dict_load() -> None: open_jtalk.use_user_dict(temp_dict) audio_query_with_dict = await synthesizer.audio_query( - "this_word_should_not_exist_in_default_dictionary", style_id=0, kana=False + "this_word_should_not_exist_in_default_dictionary", style_id=0 ) assert audio_query_without_dict != audio_query_with_dict diff --git a/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi b/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi index 324ea0117..b4e44c8e2 100644 --- a/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi +++ b/crates/voicevox_core_python_api/python/voicevox_core/_rust.pyi @@ -147,46 +147,80 @@ class Synthesizer: モデルが読み込まれているかどうか。 """ ... + async def audio_query_from_kana( + self, + kana: str, + style_id: int, + ) -> AudioQuery: + """ + AquesTalk風記法から :class:`AudioQuery` を生成する。 + + Parameters + ---------- + kana + AquesTalk風記法。 + style_id + スタイルID。 + + Returns + ------- + 話者とテキストから生成された :class:`AudioQuery` 。 + """ + ... async def audio_query( self, text: str, style_id: int, - kana: bool = False, ) -> AudioQuery: """ - :class:`AudioQuery` を生成する。 + 日本語のテキストから :class:`AudioQuery` を生成する。 Parameters ---------- text - テキスト。文字コードはUTF-8。 + UTF-8の日本語テキスト。 style_id スタイルID。 - kana - ``text`` をAquesTalk風記法として解釈するかどうか。 Returns ------- 話者とテキストから生成された :class:`AudioQuery` 。 """ ... + async def create_accent_phrases_from_kana( + self, + kana: str, + style_id: int, + ) -> List[AccentPhrase]: + """ + AquesTalk風記法からAccentPhrase(アクセント句)の配列を生成する。 + + Parameters + ---------- + kana + AquesTalk風記法。 + style_id + スタイルID。 + + Returns + ------- + :class:`AccentPhrase` の配列。 + """ + ... async def create_accent_phrases( self, text: str, style_id: int, - kana: bool = False, ) -> List[AccentPhrase]: """ - AccentPhrase(アクセント句)の配列を生成する。 + 日本語のテキストからAccentPhrase(アクセント句)の配列を生成する。 Parameters ---------- text - UTF-8の日本語テキストまたはAquesTalk風記法。 + UTF-8の日本語テキスト。 style_id スタイルID。 - kana - ``text`` をAquesTalk風記法として解釈するかどうか。 Returns ------- @@ -274,24 +308,40 @@ class Synthesizer: WAVデータ。 """ ... + async def tts_from_kana( + self, + kana: str, + style_id: int, + enable_interrogative_upspeak: bool = True, + ) -> bytes: + """ + AquesTalk風記法から音声合成を行う。 + + Parameters + ---------- + kana + AquesTalk風記法。 + style_id + スタイルID。 + enable_interrogative_upspeak + 疑問文の調整を有効にするかどうか。 + """ + ... async def tts( self, text: str, style_id: int, - kana: bool = False, enable_interrogative_upspeak: bool = True, ) -> bytes: """ - テキスト音声合成を実行する。 + 日本語のテキストから音声合成を行う。 Parameters ---------- text - UTF-8の日本語テキストまたはAquesTalk風記法。 + UTF-8の日本語テキスト。 style_id スタイルID。 - kana - ``text`` をAquesTalk風記法として解釈するかどうか。 enable_interrogative_upspeak 疑問文の調整を有効にするかどうか。 diff --git a/crates/voicevox_core_python_api/src/lib.rs b/crates/voicevox_core_python_api/src/lib.rs index bd5406436..42d3feaeb 100644 --- a/crates/voicevox_core_python_api/src/lib.rs +++ b/crates/voicevox_core_python_api/src/lib.rs @@ -14,8 +14,8 @@ use pyo3::{ use tokio::{runtime::Runtime, sync::Mutex}; use uuid::Uuid; use voicevox_core::{ - AccelerationMode, AccentPhrasesOptions, AudioQueryModel, AudioQueryOptions, InitializeOptions, - StyleId, SynthesisOptions, TtsOptions, UserDictWord, VoiceModelId, + AccelerationMode, AudioQueryModel, InitializeOptions, StyleId, SynthesisOptions, TtsOptions, + UserDictWord, VoiceModelId, }; static RUNTIME: Lazy = Lazy::new(|| Runtime::new().unwrap()); @@ -208,14 +208,35 @@ impl Synthesizer { .is_loaded_voice_model(&VoiceModelId::new(voice_model_id.to_string()))) } - #[pyo3(signature=(text,style_id,kana = AudioQueryOptions::default().kana))] - fn audio_query<'py>( + fn audio_query_from_kana<'py>( &self, - text: &str, + kana: &str, style_id: u32, - kana: bool, py: Python<'py>, ) -> PyResult<&'py PyAny> { + let synthesizer = self.synthesizer.get()?.clone(); + let kana = kana.to_owned(); + pyo3_asyncio::tokio::future_into_py_with_locals( + py, + pyo3_asyncio::tokio::get_current_locals(py)?, + async move { + let audio_query = synthesizer + .lock() + .await + .audio_query_from_kana(&kana, StyleId::new(style_id)) + .await + .into_py_result()?; + + Python::with_gil(|py| { + let class = py.import("voicevox_core")?.getattr("AudioQuery")?; + let ret = to_pydantic_dataclass(audio_query, class)?; + Ok(ret.to_object(py)) + }) + }, + ) + } + + fn audio_query<'py>(&self, text: &str, style_id: u32, py: Python<'py>) -> PyResult<&'py PyAny> { let synthesizer = self.synthesizer.get()?.clone(); let text = text.to_owned(); pyo3_asyncio::tokio::future_into_py_with_locals( @@ -225,7 +246,7 @@ impl Synthesizer { let audio_query = synthesizer .lock() .await - .audio_query(&text, StyleId::new(style_id), &AudioQueryOptions { kana }) + .audio_query(&text, StyleId::new(style_id)) .await .into_py_result()?; @@ -238,12 +259,41 @@ impl Synthesizer { ) } - #[pyo3(signature=(text, style_id, kana = AccentPhrasesOptions::default().kana))] + fn create_accent_phrases_from_kana<'py>( + &self, + kana: &str, + style_id: u32, + py: Python<'py>, + ) -> PyResult<&'py PyAny> { + let synthesizer = self.synthesizer.get()?.clone(); + let kana = kana.to_owned(); + pyo3_asyncio::tokio::future_into_py_with_locals( + py, + pyo3_asyncio::tokio::get_current_locals(py)?, + async move { + let accent_phrases = synthesizer + .lock() + .await + .create_accent_phrases_from_kana(&kana, StyleId::new(style_id)) + .await + .into_py_result()?; + Python::with_gil(|py| { + let class = py.import("voicevox_core")?.getattr("AccentPhrase")?; + let accent_phrases = accent_phrases + .iter() + .map(|ap| to_pydantic_dataclass(ap, class)) + .collect::>>(); + let list = PyList::new(py, accent_phrases.into_iter()); + Ok(list.to_object(py)) + }) + }, + ) + } + fn create_accent_phrases<'py>( &self, text: &str, style_id: u32, - kana: bool, py: Python<'py>, ) -> PyResult<&'py PyAny> { let synthesizer = self.synthesizer.get()?.clone(); @@ -255,11 +305,7 @@ impl Synthesizer { let accent_phrases = synthesizer .lock() .await - .create_accent_phrases( - &text, - StyleId::new(style_id), - &AccentPhrasesOptions { kana }, - ) + .create_accent_phrases(&text, StyleId::new(style_id)) .await .into_py_result()?; Python::with_gil(|py| { @@ -350,23 +396,53 @@ impl Synthesizer { ) } + #[pyo3(signature=( + kana, + style_id, + enable_interrogative_upspeak = TtsOptions::default().enable_interrogative_upspeak + ))] + fn tts_from_kana<'py>( + &self, + kana: &str, + style_id: u32, + enable_interrogative_upspeak: bool, + py: Python<'py>, + ) -> PyResult<&'py PyAny> { + let style_id = StyleId::new(style_id); + let options = TtsOptions { + enable_interrogative_upspeak, + }; + let synthesizer = self.synthesizer.get()?.clone(); + let kana = kana.to_owned(); + pyo3_asyncio::tokio::future_into_py_with_locals( + py, + pyo3_asyncio::tokio::get_current_locals(py)?, + async move { + let wav = synthesizer + .lock() + .await + .tts_from_kana(&kana, style_id, &options) + .await + .into_py_result()?; + Python::with_gil(|py| Ok(PyBytes::new(py, &wav).to_object(py))) + }, + ) + } + #[pyo3(signature=( text, style_id, - kana = TtsOptions::default().kana, enable_interrogative_upspeak = TtsOptions::default().enable_interrogative_upspeak ))] fn tts<'py>( &self, text: &str, style_id: u32, - kana: bool, enable_interrogative_upspeak: bool, py: Python<'py>, ) -> PyResult<&'py PyAny> { let style_id = StyleId::new(style_id); let options = TtsOptions { - kana, enable_interrogative_upspeak, }; let synthesizer = self.synthesizer.get()?.clone();