detamamoruのブログ

興味を持ったことや勉強したことに関して記事を書きます。主に低レイヤー寄りの記事を公開。

Voice changerを作りたくて(2): とりあえず音声信号入力したれや

出田 守です。
このシリーズは、ほとんど何もわからない状況からVoice changerを作る過程の記録です。

環境(私も含めて)

[私について]
算数・数学: 苦手で避けてきた(後悔中)。おそらく中学2年生で止まっている。
プログラミング: 努力中
[開発環境 - PC0]
OS: Windows10 Home 64bit 1903
CPU: Intel Core i5-3470 @ 3.20GHz
Rust: 1.39.0
RAM: 8.00GB
Editor: Vim 8.1.1
Terminal: PowerShell

音声信号入力

マイク買いました

音声信号入力用のマイクを買っちゃいました。めちゃ安いやつ。
iBUFFALO マイクロフォン ミニクリップ ブラック BSHSM03BK

コーデックの種類

以下を参照。今回はwavファイルを対象にするつもりです。
音声コーデックの種類と違い(MP3・AAC・WMA・WAV・Vorbis・AC3・FLAC等)【フォーマット】 | AviUtlの易しい使い方

Audacityで録音

Audacity 2.3.2で録音し、wavファイルとして書き出します。今はこのwavファイルを読み込んで、解析しようと思います。
いずれは、リアルタイムにしたいと思います。つまり、音声入力 -> 解析 -> 加工 -> 再構築 -> 音声出力をファイルを書き出さずにやりたいです。

rodio 0.9.0

音声再生用のクレートです。今回は外部クレートを使ってwavファイルをデコードしてもらったりしますが、いつか自力でデコードしてみたいですね。
crates.io - rodio


wavを再生するは、以下のようにするとうまくいきました。他にもplay_rawメソッドとかplay_onceメソッドとかあるんですが、実行するとコンピュータのわけわからんとこから「ボシュッ」て聞こえるだけでした。

# Cargo.toml
...
[dependencies]
rodio = "0.9.0"
// main.rs
use std::fs::File;
use std::io;

fn main() {
    let device = rodio::default_output_device().unwrap();
    let sink = rodio::Sink::new(&device);

    let file = File::open("sample_audio/sample1.wav").unwrap();
    let source = rodio::Decoder::new(io::BufReader::new(file)).unwrap();
    sink.append(source);

    sink.sleep_until_end();
}

最初にデフォルトで使用されている出力デバイスを指定します。次にデバイスをSink型で作成します。Sink型は音声出力用のハンドルです。
次にwavファイルをオープンしてBufReaderとしてDecoderに渡します。このメソッドの戻り値はIteratorトレイトを継承するSampleトレイトをItemに持つSourceトレイトが実装されたイテレータです。

pub trait Source: Iterator where
Self::Item: Sample, {
    ...
}

plotlib 0.4.0

plotlibはグラフ描画用のクレートです。svgファイルで出力されます。

crates.io - plotlib

グラフも見れた方が良いと思いいろんなクレート(gnuplotなど)を試しましたが、Windows環境ではplotlibがやっとこさうまくいきました。他のクレートでもやり方が間違ってなければうまくいくのかもしれません。
plotlib 0.4.0での線グラフの書き方は以下のようにするとうまくいきました。

use plotlib::page::Page;
use plotlib::view::ContinuousView;
use plotlib::style::Line;

fn main() {
    let mut style = plotlib::line::Style::new();
    let l1 = plotlib::line::Line::new(&[(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)]).style(style.colour("#000000"));
    let v = ContinuousView::new().add(&l1);
    Page::single(&v).save("line.svg").expect("saving svg");
}

スタイルを指定し、色を設定しないと肝心のデータ部分が表示されませんでした。おそらくデフォルトが背景色と同じ白なのかもしれません。

rodioで取得したサンプルをグラフに描画するプログラムは以下のように書きました。

use std::fs::File;
use std::io;
use rodio::source::Source;
use plotlib::page::Page;
use plotlib::view::ContinuousView;
use plotlib::style::Line;
use failure::Error;

fn draw_spectrum(data: &Vec<(f64, f64)>, outpath: &str) -> Result<(), Error> {
    let mut style = plotlib::line::Style::new();
    let li = plotlib::line::Line::new(&data)
        .style(style.colour("#000000"));
    let v = ContinuousView::new().add(&li);
    Page::single(&v).save(outpath)?;
    Ok(())
}

fn main() {
    let device = rodio::default_output_device().unwrap();
    let sink = rodio::Sink::new(&device);

    let file = File::open("sample_audio/sample1.wav").unwrap();
    let source  = rodio::Decoder::new(io::BufReader::new(file)).unwrap().buffered();

    let data: Vec<(f64, f64)> = source.clone().enumerate().map(|(i, x)| (i as f64, x as f64)).collect();
    draw_spectrum(&data, "line.svg").expect("Failed draw graph");

    println!("{}", source.sample_rate());

    sink.append(source.clone());
    sink.sleep_until_end();
}

下の画像はsvgファイルをpngファイルに変換して載せています。

f:id:detamamoru:20191114183408p:plain
line.png
これで、どのような波形なのか確認することができます。
めちゃ適当に思いつくままに作っているのですが、果たしてゴールにたどり着けるのでしょうか。(笑)