|
1 |
| -use std::{ffi::CString, path::Path}; |
| 1 | +use std::{ |
| 2 | + collections::HashMap, |
| 3 | + ffi::CString, |
| 4 | + path::Path, |
| 5 | + ptr::NonNull, |
| 6 | + sync::{Arc, LazyLock}, |
| 7 | +}; |
2 | 8 |
|
3 | 9 | use camino::Utf8Path;
|
| 10 | +use duplicate::duplicate_item; |
| 11 | +use easy_ext::ext; |
4 | 12 | use ref_cast::ref_cast_custom;
|
5 | 13 | use voicevox_core::{InitializeOptions, Result, SpeakerMeta, VoiceModelId};
|
6 | 14 |
|
7 | 15 | use crate::{
|
8 |
| - helpers::CApiResult, OpenJtalkRc, VoicevoxOnnxruntime, VoicevoxSynthesizer, |
| 16 | + helpers::CApiResult, |
| 17 | + object::{CApiObject, CApiObjectPtrExt as _}, |
| 18 | + OpenJtalkRc, VoicevoxOnnxruntime, VoicevoxSynthesizer, VoicevoxUserDict, |
9 | 19 | VoicevoxVoiceModelFile,
|
10 | 20 | };
|
11 | 21 |
|
@@ -61,61 +71,93 @@ macro_rules! to_cstr {
|
61 | 71 | use to_cstr;
|
62 | 72 |
|
63 | 73 | impl OpenJtalkRc {
|
64 |
| - pub(crate) fn new(open_jtalk_dic_dir: impl AsRef<Utf8Path>) -> Result<Self> { |
65 |
| - Ok(Self { |
66 |
| - open_jtalk: voicevox_core::blocking::OpenJtalk::new(open_jtalk_dic_dir)?, |
67 |
| - }) |
| 74 | + pub(crate) fn new(open_jtalk_dic_dir: impl AsRef<Utf8Path>) -> Result<NonNull<Self>> { |
| 75 | + let body = voicevox_core::blocking::OpenJtalk::new(open_jtalk_dic_dir)?; |
| 76 | + Ok(<Self as CApiObject>::new(body)) |
68 | 77 | }
|
69 | 78 | }
|
70 | 79 |
|
71 | 80 | impl VoicevoxSynthesizer {
|
72 | 81 | pub(crate) fn new(
|
73 | 82 | onnxruntime: &'static VoicevoxOnnxruntime,
|
74 |
| - open_jtalk: &OpenJtalkRc, |
| 83 | + open_jtalk: *const OpenJtalkRc, |
75 | 84 | options: &InitializeOptions,
|
76 |
| - ) -> Result<Self> { |
77 |
| - let synthesizer = voicevox_core::blocking::Synthesizer::new( |
| 85 | + ) -> Result<NonNull<Self>> { |
| 86 | + let body = voicevox_core::blocking::Synthesizer::new( |
78 | 87 | &onnxruntime.0,
|
79 |
| - open_jtalk.open_jtalk.clone(), |
| 88 | + open_jtalk.body().clone(), |
80 | 89 | options,
|
81 | 90 | )?;
|
82 |
| - Ok(Self { synthesizer }) |
| 91 | + Ok(<Self as CApiObject>::new(body)) |
83 | 92 | }
|
| 93 | +} |
84 | 94 |
|
85 |
| - pub(crate) fn onnxruntime(&self) -> &'static VoicevoxOnnxruntime { |
86 |
| - VoicevoxOnnxruntime::new(self.synthesizer.onnxruntime()) |
| 95 | +#[ext(VoicevoxSynthesizerPtrExt)] |
| 96 | +impl *const VoicevoxSynthesizer { |
| 97 | + pub(crate) fn onnxruntime(self) -> &'static VoicevoxOnnxruntime { |
| 98 | + VoicevoxOnnxruntime::new(self.body().onnxruntime()) |
87 | 99 | }
|
88 | 100 |
|
89 | 101 | pub(crate) fn load_voice_model(
|
90 |
| - &self, |
| 102 | + self, |
91 | 103 | model: &voicevox_core::blocking::VoiceModelFile,
|
92 | 104 | ) -> CApiResult<()> {
|
93 |
| - self.synthesizer.load_voice_model(model)?; |
| 105 | + self.body().load_voice_model(model)?; |
94 | 106 | Ok(())
|
95 | 107 | }
|
96 | 108 |
|
97 |
| - pub(crate) fn unload_voice_model(&self, model_id: VoiceModelId) -> Result<()> { |
98 |
| - self.synthesizer.unload_voice_model(model_id)?; |
| 109 | + pub(crate) fn unload_voice_model(self, model_id: VoiceModelId) -> Result<()> { |
| 110 | + self.body().unload_voice_model(model_id)?; |
99 | 111 | Ok(())
|
100 | 112 | }
|
101 | 113 |
|
102 |
| - pub(crate) fn metas(&self) -> CString { |
103 |
| - metas_to_json(&self.synthesizer.metas()) |
| 114 | + pub(crate) fn metas(self) -> CString { |
| 115 | + metas_to_json(&self.body().metas()) |
104 | 116 | }
|
105 | 117 | }
|
106 | 118 |
|
107 | 119 | impl VoicevoxVoiceModelFile {
|
108 |
| - pub(crate) fn open(path: impl AsRef<Path>) -> Result<Self> { |
| 120 | + pub(crate) fn open(path: impl AsRef<Path>) -> Result<NonNull<Self>> { |
109 | 121 | let model = voicevox_core::blocking::VoiceModelFile::open(path)?;
|
110 |
| - Ok(Self { model }) |
| 122 | + Ok(Self::new(model)) |
111 | 123 | }
|
| 124 | +} |
112 | 125 |
|
113 |
| - pub(crate) fn metas(&self) -> CString { |
114 |
| - metas_to_json(self.model.metas()) |
| 126 | +#[ext(VoicevoxVoiceModelFilePtrExt)] |
| 127 | +impl *const VoicevoxVoiceModelFile { |
| 128 | + pub(crate) fn metas(self) -> CString { |
| 129 | + metas_to_json(self.body().metas()) |
115 | 130 | }
|
116 | 131 | }
|
117 | 132 |
|
118 | 133 | fn metas_to_json(metas: &[SpeakerMeta]) -> CString {
|
119 | 134 | let metas = serde_json::to_string(metas).expect("should not fail");
|
120 | 135 | CString::new(metas).expect("should not contain NUL")
|
121 | 136 | }
|
| 137 | + |
| 138 | +#[duplicate_item( |
| 139 | + H B; |
| 140 | + [ OpenJtalkRc ] [ voicevox_core::blocking::OpenJtalk ]; |
| 141 | + [ VoicevoxUserDict ] [ voicevox_core::blocking::UserDict ]; |
| 142 | + [ VoicevoxSynthesizer ] [ voicevox_core::blocking::Synthesizer<voicevox_core::blocking::OpenJtalk> ]; |
| 143 | + [ VoicevoxVoiceModelFile ] [ voicevox_core::blocking::VoiceModelFile ]; |
| 144 | +)] |
| 145 | +impl CApiObject for H { |
| 146 | + type RustApiObject = B; |
| 147 | + |
| 148 | + fn heads() -> &'static std::sync::Mutex<Vec<Self>> { |
| 149 | + static HEADS: std::sync::Mutex<Vec<H>> = std::sync::Mutex::new(vec![]); |
| 150 | + &HEADS |
| 151 | + } |
| 152 | + |
| 153 | + fn bodies() -> &'static std::sync::Mutex< |
| 154 | + HashMap<usize, Arc<parking_lot::RwLock<Option<Self::RustApiObject>>>>, |
| 155 | + > { |
| 156 | + #[expect(clippy::type_complexity, reason = "`CApiObject::bodies`と同様")] |
| 157 | + static BODIES: LazyLock< |
| 158 | + std::sync::Mutex<HashMap<usize, Arc<parking_lot::RwLock<Option<B>>>>>, |
| 159 | + > = LazyLock::new(Default::default); |
| 160 | + |
| 161 | + &BODIES |
| 162 | + } |
| 163 | +} |
0 commit comments