vocaloid 0.1.3

Real-time voice synthesis interface for rasberrypi OSAI Vocaloid systems.
Documentation
use hound::{WavReader, WavWriter, WavSpec, SampleFormat};
//use std::io::{self, Write};
mod roma_parser;
use roma_parser::split_romaji;
use fileio_handle::FileIO;
use crate::roma_parser::hiragana_to_romaji;


pub fn wav_shorter(input_path: &str, output_path: &str, ratio: f32) -> hound::Result<()> {
    let mut reader = WavReader::open(input_path)?;
    let spec = reader.spec();
    let samples: Vec<i16> = reader.samples::<i16>().map(|s| s.unwrap()).collect();

    let shorter_len = ((samples.len() as f32 * ratio).min(samples.len() as f32)) as usize;
    let shorter_samples = &samples[..shorter_len];

    let mut writer = WavWriter::create(output_path, spec)?;
    for &sample in shorter_samples {
        writer.write_sample(sample)?;
    }
    writer.finalize()?;
    Ok(())
}

pub fn adjust_pitch(input_path: &str, output_path: &str, pitch_ratio: f32) -> hound::Result<()> {
    let mut reader = WavReader::open(input_path)?;
    let spec = reader.spec();
    let samples: Vec<i16> = reader.samples::<i16>().map(|s| s.unwrap()).collect();

    // 単純リサンプリング(間引き or 重複で速度調整)
    let new_len = (samples.len() as f32 / pitch_ratio) as usize;
    let mut new_samples = Vec::with_capacity(new_len);

    for i in 0..new_len {
        let orig_idx = (i as f32 * pitch_ratio) as usize;
        let idx = if orig_idx < samples.len() { orig_idx } else { samples.len() -1 };
        new_samples.push(samples[idx]);
    }

    let mut writer = WavWriter::create(output_path, spec)?;
    for sample in new_samples {
        writer.write_sample(sample)?;
    }
    //短くする分そこに同じ音を入れれば良い
    writer.finalize()?;
    Ok(())
}

pub fn emotion_to_pitch(p: &[f32]) -> f32 {
    //14このパラメーターのうち7こはプラスなのでpitchは高くゆっくりに
    if p.is_empty() {
        return 1.0;
    }
    let half = p.len() / 2;
    let mut emotion = 0.0;
    for (i, val) in p.iter().enumerate() {
        if i >= half {
            emotion += val;
        }else{
            emotion -= val;
        }
    }
    let ratio = 1.0 + emotion / 1000.0 / 7.0;
    ratio.clamp(0.5, 1.1)
}


pub fn create_word_wav(word: &str, temp: &str) -> hound::Result<()>{
    let syllables = split_romaji(word);
    let _out_file = format!("{}.wav", word); // 変数名の先頭にアンダースコアを追加して警告を抑制
    let spec = WavReader::open(format!("voice/__{}.wav", syllables[0]))?.spec();
    let mut writer = WavWriter::create(temp, spec)?;

    for s in syllables {
        let input_file = format!("voice/__{}.wav", s);    
        let render = WavReader::open(&input_file)?;
        for sample in render.into_samples::<i16>() {
            writer.write_sample(sample?)?;    
        }
    }
    writer.finalize()?;
    Ok(())
}

pub fn create_wav(words: &[String], params: &[Vec<f32>]) -> hound::Result<()> {
    let mut input_files = Vec::new();
    std::fs::create_dir_all("temp")?;

    for (i, word) in words.iter().enumerate() {
        let temp_main = format!("temp/word_{}.wav", i);

        if word == " " {
            let silence_file = "voice/silence.wav";
            std::fs::copy(silence_file, &temp_main)?;
        } else {
            create_word_wav(word, &temp_main)?;
        }

        let adjusted_file = format!("temp/pitched_{}.wav", i);
        let pitch = emotion_to_pitch(&params[i]);
        println!("{}", pitch);

        wav_shorter(&temp_main, &adjusted_file, pitch)?;
        adjust_pitch(&adjusted_file, &adjusted_file, pitch)?;
        input_files.push(adjusted_file);
    }

    // 最後に結合
    let output_file = "output.wav";
    let spec = WavReader::open(&input_files[0])?.spec();
    let mut output = WavWriter::create(output_file, spec)?;

    for file in &input_files {
        let reader = WavReader::open(file)?;
        for sample in reader.into_samples::<i16>() {
            output.write_sample(sample?)?;
        }
    }

    output.finalize()?;
    println!("結合完了: {}", output_file);
    Ok(())
}

pub fn emotion_vocaloid() -> Result<(), hound::Error>{
    let mut file = FileIO::new("lyric.txt");
    file.read_lines()?;
    let sep_list = [","];
    let mut words = Vec::new();
    let mut params = Vec::new();

    for line in &file.contents {
        let splits = FileIO::phaser(line, &sep_list);
        if splits.is_empty() {
            continue;
        }

        let word = splits[0].clone();
        let param: Vec<f32> = splits[1..]
            .iter()
            .filter_map(|s| s.parse::<f32>().ok())
            .collect();

        words.push(word);
        params.push(param);
    }

    println!("{:?}", words);
    println!("{:?}", params);
    
    // 修正2: words (Vec<String>, 日本語の単語リスト)を
    // イテレートして、一つずつhiragana_to_romajiに渡し、RomajiのVec<String>を生成
    let roma_c: Vec<String> = words.iter()
        .map(|word| hiragana_to_romaji(word))
        .collect();

    println!("{:?}", roma_c);
    create_wav(&roma_c, &params)?;
    Ok(())

}


// 修正1: Rustの関数引数は `変数名: 型` の形式で定義します
pub fn vocaloid(text: &str) -> Result<(), hound::Error> {

    let input = hiragana_to_romaji(text);
    println!("{}", input);
    let words: Vec<String> = input
        .trim()
        .split_whitespace()
        .map(|s| s.to_string())
        .collect();

    let params: Vec<Vec<f32>> = vec![vec![5.0; 14]; words.len()];  // 感情パラメータ無し=フラット

    create_wav(&words, &params)?;
    Ok(())
}