コレはなに?
貴方の歌声に自動的にバックコーラス(疑似)をおつけします。
どういうこと?
音声データのピッチを解析し、そのピッチの倍音成分を基本周波数とする正弦波信号を作ってくれるスクリプトをMATLABで作成しました。
動いている様子
どうぞ。
歌っている部分のみ抽出というところはこの動画で試してる手法があんまりよくないので後で書き直しました。
ちゃんと最後に実行しているので下記のMovieで実行結果を音で確認できますよ。
今回は著作権が切れているアメリカ合衆国国歌の出だしを私自身が歌ってみました。
何かまたよくわからないものを作ってしまった。 pic.twitter.com/7PwY4lNTbq
— griffin921 (@griffin921) November 24, 2021
解説
このスクリプトは大きく5つ処理をやってます。
1.オーディオ情報を入力
2.オーディオ情報からピッチ情報を推定
3.歌っている部分のみを特定
4.Pichで解析した周波数x0.5,x2,x3の正弦波信号、すなわち擬似バックコーラス音声を生成
5.元の音声データと並行して再生
1.オーディオ情報を入力
audioread関数というのをつかったら簡単に音声情報を取り込めました。
たった1行・・・
[audioIn, fs] = audioread('Myvoice.wav');
音声ファイルを作るもの結構簡単で、こちらのヘルプドキュメントを見ながらスクリプト書いたらすぐできました。
2.オーディオ情報からピッチ情報を推定
なんか難しそうじゃないですか。ピッチとか。
音声処理僕は専門じゃないのでよくわからないし。
とおもったら単一の関数で解析できました・・・
引数に解析の窓幅とオーバラップ長、そしてピッチとして解析する周波数レンジを指定して音声データとサンプリング周波数を入れてあげたら
データを返してくれます。
%窓幅とオーバラップ幅を指定
windowLength = round(0.1*fs);
overlapLength = 0;
%ピッチの推定
f0 = pitch(audioIn,fs,'WindowLength',windowLength,'OverlapLength',overlapLength,'Range',[50,250]);
3.歌っている部分のみを特定
歌っていない部分は250Hzというピッチ解析レンジの上限に張り付いているので、歌ってる部分だけをなんとか特定したいですね。
というわけで音声パワーをデシベルでみたとき,
-40dB以上のパワーを持つ領域だけ判定してくれるようなスクリプトを書きました。
%音声データのうち、下記の変数[dB]以上のパワーを持つ領域が歌っている領域とする
pwrThreshold = -40;%dB
%窓をシフトさせながらデータを取得
[segments,~] = buffer(audioIn,windowLength,overlapLength,'nodelay');
%パワーをdBで取得し、閾値と比較
pwr = pow2db(var(segments));
isVoiced = (pwr > pwrThreshold);
##4.Pichで解析した周波数x0.5,x2,x3の正弦波信号、すなわち擬似バックコーラス音声を生成
なんかここまでいくとできそうです。
3.で作ったisVoicedがtrueの時間領域だけピッチ情報を元に正弦波信号を作れば良さそうです。
ここではコーラス音声を作りたいので元のピッチの0.5倍、2倍、3倍の周波数を持つ正弦波をつくってみました。
ここらへんはもっと良い書き方がありそうなのですがまぁ妥協しましたw
%AudioINデータをピッチ解析データ数だけで分割すると
%分割されたバッファサイズがいくつかを計算する
buffer_size = int32((numel(audioIn)/ numel(f0)));
%バッファ用のTimeVectorを作成
BufferTimeVector = timeVector(1:buffer_size)';
%出力音声データのデータ配列を作成
BackChorusA = zeros(numel(audioIn),1);
BackChorusB = zeros(numel(audioIn),1);
BackChorusC = zeros(numel(audioIn),1);
%データ数でfor文でイテレーションを回す
for itr = 0:numel(f0)-1
%isVoicedがtrueのときだけ疑似コーラス信号を作る
if(isVoiced(itr+1))
%イテレーションごとにピッチに対して0.5倍の周波数で振動する正弦波信号データを作る
thisSinwaveData = sin(2*pi*0.5*f0(itr)*BufferTimeVector) * 0.8;
BuckChorusA(1+itr*buffer_size:(itr+1)*buffer_size) = thisSinwaveData;
%イテレーションごとにピッチに対して2倍の周波数で振動する正弦波信号データを作る
thisSinwaveData = sin(2*pi*2*f0(itr)*BufferTimeVector) * 0.8;
BuckChorusB(1+itr*buffer_size:(itr+1)*buffer_size) = thisSinwaveData;
%イテレーションごとにピッチに対して3倍の周波数で振動する正弦波信号データを作る
thisSinwaveData = sin(2*pi*3*f0(itr)*BufferTimeVector) * 0.8;
BuckChorusC(1+itr*buffer_size:(itr+1)*buffer_size) = thisSinwaveData;
end
end
5.元の音声データと並行して再生
なんとこれで4つ並行して音声を再生してくれます。
%自分の歌声を再生
sound(audioIn, fs);
%コーラスA再生
sound(BuckChorusA, fs);
%コーラスB再生
sound(BuckChorusB, fs);
%コーラスC再生
sound(BuckChorusC, fs)
(追記)
さらに各コーラスの周波数を調整して、和音となるようにしてみました。
色々改善点はありますが、うまくいってるみたい。
7thコードコーラスできたぞ。
— griffin921 (@griffin921) November 24, 2021
コーラスの音がプツプツいうのはバッファごとに正弦波をつくっているからで、バッファとバッファの切れ目で非線形な波形になってるので高周波が乗るんだと思う。
charpを使えばなめらかに周波数遷移できるが、こうなると音を「フレーズ単位で分割」する必要があるので沼 pic.twitter.com/gqvLAPR4KK
まとめ
MATLABのいいところはこういうネタを思いついてからソッコーで実装できるところにあります。
これ、思いついてから動くまで1時間半でした。
コレ見て「全然できてないじゃん。ツッコミどころ満載だよ」という方もいらっしゃると思います。たしかにそうですが、未完成でもとりあえず動くものをつくらないと何も始まりませんし、不完全さが具体化してはじめて不足している部分を補うものがなにかわかり、その何かを埋めるためのアプローチについて仮説を立てることができるようになります。
この 書く→動く→検証→仮説立案→書く→動く→検証・・・のサイクルを早く回すことに非常にメリットが有ると思います。
音声処理についてあまり詳しくない僕がここまでできたのはMATLABのおかげかも・・・
使用したツールボックス
Audio Toolbox
Signal Processing Toolbox
ちなみにMATLABでスクリプト書くときに、Projectにまとめておくと、このスクリプトがどんなToolboxを利用しているか解析してくれるので地味に便利です。
Projectの依存関係アナライザーを使ったら、Projectに含まれるスクリプトがどのToolbox使ってるかすぐにわかります。 pic.twitter.com/cTQLN4IguU
— griffin921 (@griffin921) November 24, 2021
以上 ありがとうございました!