Tokyo.R #22 に行ってきた& R で音声合成やってみた

以前より一度参加してみたいと思っていた Tokyo.R #22 にのこのこ行ってきた。主催の @yokkuns さん、参加者&発表者のみなさん、会場を提供してくださったニフティさん、ありがとうございました&お疲れさまでした。


atnd に LT 募集中とあったので、初参加だけど「Rで音声合成やってみた」というタイトルでちょこっとしゃべってきた。資料はこれ一枚。手書き。

音声合成といっても、読み込んだ音声データを FFT(高速フーリエ変換)にかけて周波数成分を取り出し、それに従って正弦波を作って足し算するだけ。だから実は音声じゃあなくてもいい。それ音声合成ちゃうし! と突っ込みは無しの方向で。
全ての音はサインカーブの和で表すことが出来るというのは知識としては知っているがやったことないなあ、R なら簡単にできそうだからやってみよう! と、以下実演。


wav ファイルの読み書きのために sound パッケージを使うので、あらかじめインストールしておく。
cutSample() で wav ファイルから適当に1秒間切り出して、波形を見てみる。

library(sound)
wav <- cutSample("[「あー」という wav ファイル]", 0.5, 1.5)
plot(wav)


このサンプリングデータに fft をかける。周波数成分はそれぞれ複素数で表現されていて、例えば 440Hz の成分は transform[440] などで参照できる。
そこで周波数成分の絶対値(振幅)を plot すればスペクトルとして見ることができ、ちゃんと倍音成分が出ていることがわかる。

transform <- fft(sound(wav)[1,])
RANGE <- 20:5000
plot(RANGE, abs(transform[RANGE]), type="l")


各周波数成分はそれぞれの周波数・振幅・位相が定めるサインカーブを表していて、その総和を単純に取ると、元の音に戻る。
元に戻ることを示すのもそれはそれでありだけど、せっかくだから振幅の大きい順に100個の成分だけ取り出すことにする。
成分が複素数で与えられていることを使うと、100個のサインカーブの総和は sapply の行のように書ける。

(extracted <- RANGE[order(-abs(transform[RANGE]))[1:100]])

trange <- seq(0, 1, length.out = 44100)
synthesized <- sapply(trange, function(t)sum(Im(transform[extracted] * exp(2i * pi * extracted * t))))
sample <- normalize(as.Sample(synthesized, 44100, 16))
plot(sample)

saveSample(sample,"wav ファイルの適当な出力パス", overwrite = TRUE)


最後に出力した wav ファイルを鳴らして、おしまい。
100個のサインカーブを足しただけで、ちゃんと「あー」に聞こえる音になる。