Merge pull request #41 from tuna2134/easy

Add easy synthesize function
This commit is contained in:
コマリン親衛隊
2024-09-13 19:29:02 +09:00
committed by GitHub
5 changed files with 158 additions and 74 deletions

39
content.txt Normal file
View File

@@ -0,0 +1,39 @@
日本語を母国語としない人々にとって、「日本語は非常に難しい言語である」と言われています。
その理由として、
・漢字、ひらがな、カタカナ、と表記が何種類もある。
・同一の漢字でも音読みと訓読みがある
・地名の読みが難しい
・主語、述語が省略される
などが挙げられます。
そして、やっと基本的な日本語を習得してもさらなる壁が立ちはだかっているのです。
例えば、「はやくいって」、この言葉がすべて平仮名で書かれていたり会話の中で出てきた場合です。
外国人のA君が大学の講義を終えてアルバイト先に向かっているとき、校門で日本人の友達B君に出会い進路相談をされ、1時間が経過してしまいます。さすがにもうアルバイトの開始時間に間に合わない!!そこでA君はB君に急いでいることを伝えると…B君から「はやくいって!」と言われました。
A君はその言葉の意味を理解しかねてしばし立ち尽くしてしまいます。
「はやくいって」は、「早く言って」と「早く行って」の両方の解釈が出来てしまうのです。
アルバイトがあって急いでいるのなら、その旨をB君に「早く言って」ほしかった。
2,アルバイトがあって急いでいるのなら、アルバイト先に「早く行って」
上記のように、複数の解釈ができる日本語が多く存在しており、「習得に時間がかかる言語である」といわれる理由の一つです。
{'input_ids': tensor([[ 2, 106, 1645, ..., 106, 2516, 108]], device='cuda:0'), 'attention_mask': tensor([1.], device='cuda:0')}
日本語には、「助数詞」と言って「数量を表現するのに用いる接尾語」があります。
この助数詞は、「種類の多さ」と「音の変化=変音現象」が特徴です。
紙→枚(まい)、車→台(だい)、列車→輌(りょう)、箸→膳(ぜん)、エンジン→基(き)
種類の多さもさることながら、もう一つの難しさが変音現象です。
例えば、カエルを数えるときに「1匹いっぴき」と読むのに対して「2匹≠にぴき」で2匹を「にぴき」とは読まずに「2匹にひき」となります。
どうしてこうなるのか。
ここには、漢語の半濁音のルール、というものが発生します。
まず、半濁音はハ行にだけしか付きません。(はひふへほ→ぱぴぷぺぽ)
そして、連濁といって語と語が合体するときに後ろに来る語の頭の部分が清音から濁音に変化します。
ハ行音の連濁のうち、前の語の最後に「つ」がくると、後ろの語の頭が半濁音に変わります。
例)切(せつ) + 腹(ふく) = 切腹(せっぷく)
 
助数詞も、数字の「一・六・八・十・百」が「ハ行音の助数詞」と結びつく時、促音便「っ(小さい”っ”)」を起こし、後ろの助数詞が半濁音となります。
1杯いち  はい  いっぱい、6泊ろく  はく  ろっぱく、8袋はち  ふくろ  はっぷくろ
10編じゅう  へん  じっぺん、100本ひゃく  ほん →ひゃっぽん
こういった理由から、「2匹→2ぴき」とはなりません。

View File

@@ -54,10 +54,10 @@ impl JTalk {
Ok(Self { jpreprocess })
}
pub fn g2p(&self, text: &str) -> Result<(Vec<String>, Vec<i32>, Vec<i32>)> {
pub fn process_text(&self, text: &str) -> Result<JTalkProcess> {
let parsed = self.jpreprocess.run_frontend(text)?;
let jtalk_process = JTalkProcess::new(Arc::clone(&self.jpreprocess), parsed);
jtalk_process.g2p()
Ok(jtalk_process)
}
}
@@ -69,7 +69,7 @@ static MORA_PATTERN: Lazy<Vec<String>> = Lazy::new(|| {
});
static LONG_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r"(\w)(ー*)").unwrap());
struct JTalkProcess {
pub struct JTalkProcess {
jpreprocess: Arc<JPreprocessType>,
parsed: Vec<String>,
}
@@ -268,7 +268,7 @@ impl JTalkProcess {
Ok(data)
}
fn text_to_seq_kata(&self) -> Result<(Vec<String>, Vec<String>)> {
pub fn text_to_seq_kata(&self) -> Result<(Vec<String>, Vec<String>)> {
let mut seq_kata = vec![];
let mut seq_text = vec![];

View File

@@ -1,4 +1,4 @@
use std::{fs, time::Instant};
use std::fs;
use sbv2_core::tts;
use std::env;
@@ -6,7 +6,7 @@ use std::env;
fn main() -> anyhow::Result<()> {
dotenvy::dotenv_override().ok();
env_logger::init();
let text = "眠たい";
let text = fs::read_to_string("content.txt")?;
let ident = "aaa";
let mut tts_holder = tts::TTSModelHolder::new(
&fs::read(env::var("BERT_MODEL_PATH")?)?,
@@ -14,44 +14,8 @@ fn main() -> anyhow::Result<()> {
)?;
tts_holder.load_sbv2file(ident, fs::read(env::var("MODEL_PATH")?)?)?;
let (bert_ori, phones, tones, lang_ids) = tts_holder.parse_text(text)?;
let audio = tts_holder.easy_synthesize(ident, &text, 0, tts::SynthesizeOptions::default())?;
fs::write("output.wav", audio)?;
let style_vector = tts_holder.get_style_vector(ident, 0, 1.0)?;
let data = tts_holder.synthesize(
ident,
bert_ori.to_owned(),
phones.clone(),
tones.clone(),
lang_ids.clone(),
style_vector.clone(),
0.0,
0.5,
)?;
std::fs::write("output.wav", data)?;
let now = Instant::now();
for _ in 0..10 {
tts_holder.parse_text(text)?;
}
println!(
"Time taken(parse_text): {}ms/it",
now.elapsed().as_millis() / 10
);
let now = Instant::now();
for _ in 0..10 {
tts_holder.synthesize(
ident,
bert_ori.to_owned(),
phones.clone(),
tones.clone(),
lang_ids.clone(),
style_vector.clone(),
0.0,
1.0,
)?;
}
println!(
"Time taken(synthesize): {}ms/it",
now.elapsed().as_millis() / 10
);
Ok(())
}

View File

@@ -1,8 +1,6 @@
use crate::error::Result;
use hound::{SampleFormat, WavSpec, WavWriter};
use ndarray::{array, s, Array1, Array2, Axis};
use ndarray::{array, Array1, Array2, Array3, Axis};
use ort::{GraphOptimizationLevel, Session};
use std::io::Cursor;
#[allow(clippy::vec_init_then_push, unused_variables)]
pub fn load_model<P: AsRef<[u8]>>(model_file: P, bert: bool) -> Result<Session> {
@@ -59,7 +57,7 @@ pub fn synthesize(
style_vector: Array1<f32>,
sdp_ratio: f32,
length_scale: f32,
) -> Result<Vec<u8>> {
) -> Result<Array3<f32>> {
let bert = bert_ori.insert_axis(Axis(0));
let x_tst_lengths: Array1<i64> = array![x_tst.shape()[0] as i64];
let x_tst = x_tst.insert_axis(Axis(0));
@@ -84,24 +82,12 @@ pub fn synthesize(
.try_extract_tensor::<f32>()?
.to_owned();
let buffer = {
let spec = WavSpec {
channels: 1,
sample_rate: 44100,
bits_per_sample: 32,
sample_format: SampleFormat::Float,
};
let mut cursor = Cursor::new(Vec::new());
let mut writer = WavWriter::new(&mut cursor, spec)?;
for i in 0..audio_array.shape()[0] {
let output = audio_array.slice(s![i, 0, ..]).to_vec();
for sample in output {
writer.write_sample(sample)?;
}
}
writer.finalize()?;
cursor.into_inner()
};
Ok(buffer)
Ok(Array3::from_shape_vec(
(
audio_array.shape()[0],
audio_array.shape()[1],
audio_array.shape()[2],
),
audio_array.into_raw_vec_and_offset().0,
)?)
}

View File

@@ -1,6 +1,7 @@
use crate::error::{Error, Result};
use crate::{bert, jtalk, model, nlp, norm, style, tokenizer, utils};
use ndarray::{concatenate, s, Array, Array1, Array2, Axis};
use hound::{SampleFormat, WavSpec, WavWriter};
use ndarray::{concatenate, s, Array, Array1, Array2, Array3, Axis};
use ort::Session;
use std::io::{Cursor, Read};
use tar::Archive;
@@ -124,7 +125,8 @@ impl TTSModelHolder {
) -> Result<(Array2<f32>, Array1<i64>, Array1<i64>, Array1<i64>)> {
let normalized_text = norm::normalize_text(text);
let (phones, tones, mut word2ph) = self.jtalk.g2p(&normalized_text)?;
let process = self.jtalk.process_text(&normalized_text)?;
let (phones, tones, mut word2ph) = process.g2p()?;
let (phones, tones, lang_ids) = nlp::cleaned_text_to_sequence(phones, tones);
let phones = utils::intersperse(&phones, 0);
@@ -134,12 +136,17 @@ impl TTSModelHolder {
*item *= 2;
}
word2ph[0] += 1;
let (token_ids, attention_masks) = tokenizer::tokenize(&normalized_text, &self.tokenizer)?;
let text = {
let (seq_text, _) = process.text_to_seq_kata()?;
seq_text.join("")
};
let (token_ids, attention_masks) = tokenizer::tokenize(&text, &self.tokenizer)?;
let bert_content = bert::predict(&self.bert, token_ids, attention_masks)?;
assert!(
word2ph.len() == normalized_text.chars().count() + 2,
word2ph.len() == text.chars().count() + 2,
"{} {}",
word2ph.len(),
normalized_text.chars().count()
@@ -197,6 +204,76 @@ impl TTSModelHolder {
style::get_style_vector(&self.find_model(ident)?.style_vectors, style_id, weight)
}
pub fn easy_synthesize<I: Into<TTSIdent> + Copy>(
&self,
ident: I,
text: &str,
style_id: i32,
options: SynthesizeOptions,
) -> Result<Vec<u8>> {
let style_vector = self.get_style_vector(ident, style_id, options.style_weight)?;
let audio_array = if options.split_sentences {
let texts: Vec<&str> = text.split('\n').collect();
let mut audios = vec![];
for (i, t) in texts.iter().enumerate() {
if t.is_empty() {
continue;
}
let (bert_ori, phones, tones, lang_ids) = self.parse_text(t)?;
let audio = model::synthesize(
&self.find_model(ident)?.vits2,
bert_ori.to_owned(),
phones,
tones,
lang_ids,
style_vector.clone(),
options.sdp_ratio,
options.length_scale,
)?;
audios.push(audio);
if i != texts.len() - 1 {
audios.push(Array3::zeros((1, 22050, 1)));
}
}
concatenate(
Axis(0),
&audios.iter().map(|x| x.view()).collect::<Vec<_>>(),
)?
} else {
let (bert_ori, phones, tones, lang_ids) = self.parse_text(text)?;
model::synthesize(
&self.find_model(ident)?.vits2,
bert_ori.to_owned(),
phones,
tones,
lang_ids,
style_vector,
options.sdp_ratio,
options.length_scale,
)?
};
Self::array_to_vec(audio_array)
}
fn array_to_vec(audio_array: Array3<f32>) -> Result<Vec<u8>> {
let spec = WavSpec {
channels: 1,
sample_rate: 44100,
bits_per_sample: 32,
sample_format: SampleFormat::Float,
};
let mut cursor = Cursor::new(Vec::new());
let mut writer = WavWriter::new(&mut cursor, spec)?;
for i in 0..audio_array.shape()[0] {
let output = audio_array.slice(s![i, 0, ..]).to_vec();
for sample in output {
writer.write_sample(sample)?;
}
}
writer.finalize()?;
Ok(cursor.into_inner())
}
#[allow(clippy::too_many_arguments)]
pub fn synthesize<I: Into<TTSIdent>>(
&self,
@@ -209,7 +286,7 @@ impl TTSModelHolder {
sdp_ratio: f32,
length_scale: f32,
) -> Result<Vec<u8>> {
let buffer = model::synthesize(
let audio_array = model::synthesize(
&self.find_model(ident)?.vits2,
bert_ori.to_owned(),
phones,
@@ -219,6 +296,24 @@ impl TTSModelHolder {
sdp_ratio,
length_scale,
)?;
Ok(buffer)
Self::array_to_vec(audio_array)
}
}
pub struct SynthesizeOptions {
sdp_ratio: f32,
length_scale: f32,
style_weight: f32,
split_sentences: bool,
}
impl Default for SynthesizeOptions {
fn default() -> Self {
SynthesizeOptions {
sdp_ratio: 0.0,
length_scale: 1.0,
style_weight: 1.0,
split_sentences: true,
}
}
}