#初めに
前回に引き続きMATLAB Simulinkを用いて音声処理を行う.今回はDelayを作る.
#背景知識
実装の前に前提となる知識を述べる.
##Delayとは何か
Delayはその名の通り,音を遅らせるエフェクトである.やまびこのような効果を得ることができる.以下の完成版を見るのが手っ取り早い.
Simulinkでデジタルディレイ,さっきよりうまく実装できた pic.twitter.com/1QgvCFdVbf
— 9W3R7Y (@qwerty_16180339) December 17, 2020
##Delayの原理
Delayでは,前回作成したDistortionと異なり,過去の音を参照する必要がある.そのため,音を保存しておくBufferを用意する必要がある.Bufferは,初期値が全て0となる長さが有限の配列で,以下のようにところてん式に過去の音が保存される.つまり,信号が送られてくるたびに現在の音が一番初めの要素に格納され,それ以前の音は一つ後ろの要素へと移動する.尚,Bufferの長さをBuffer sizeと呼ぶ.
図の一番上は時刻が0の状態を表しており,その下はそれぞれ時刻が1, 2, 3の時を表している.$x_t$は時刻がtの時の信号の大きさを表している.ここで,Bufferの黄色く塗ってある要素,すなわちindexが(0から数えたときに)2となる要素は,常に時刻が2つ前のサンプルを表していることが分かる.indexが3となる要素は,時刻が3つ前のサンプルを表している.このように,Bufferに音を溜め,任意のインデックスの要素を取り出すことで,Delayを実装することができる.
##Simulinkでの音の扱いについて
前回も述べたが,Simulinkでは音をある程度の長さを持った音を扱っており,一つ一つのサンプルがブロック間を送受信されているわけではない.1フレームの間にブロック間を送受信されるサンプルの塊の長さはframe sizeと呼ばれる.
#基本的なDelayの実装
ここからは実際にDelayを実装する.
##ステレオの音声をL・Rそれぞれの音声に分離
Delayの実装ではindexの指定を多く行う.ステレオだと2次元で面倒なので,モノの音声に分離して1次元にしておく.
以下のようにブロックを配置する.ステレオ音源,MATLAB Functionブロック,スピーカーの順につなぐ.MATLAB FunctionブロックはStereo Separationに名前を変更しておく.
次にStereo Separationのコードを以下のように書き換える.
function [L,R] = fcn(stereo)
L = stereo(:,1);
R = stereo(:,2);
##Bufferの実装
以下のようにMATLAB Functionブロックを追加し,名前をDelayに変更する.
これ以降は,Delayブロックを書き換える.
まずは,Buffer sizeを表す変数buff_size
を初期化する.今回は適当に100,000とする.フレームサイズを表す変数frame_size
はlength(u)
で初期化する.
次に,Bufferの配列buff
を定義する.通常,関数内で定義された変数は,呼び出し毎に初期化されてしまうので,buff
の定義の際にはPersistent
な変数(配列)であると明示する必要がある.変数は,定義した段階では空の状態なので,isempty(buff)
でbuff
が定義された直後であることを判別できる.isempty(buff)
が成り立つ場合には,buff = zeros(buff_size,1)
として初期化する.
次に,buff
に信号を保存する処理を実装する.この処理では信号が送られてくるたびに現在の入力信号を一番初めの要素に格納し,それ以前の音は一つ後ろの要素へと移動させればよい.ただし,現在の入力はframe_size
分の長さを持った配列であることに注意すべきである.積まれた本を上から順に取り,隣に積んでいくと,本の順序は逆になる.同様に,入力の配列u
をbuff
に追加する際には,flip()
を用いてu
の順番を逆にする必要がある.
また,一度の入力でframe_size
だけbuff
の要素が動くことにも注意する必要がある.この場合には,buff
のframe_size + 1
番目からbuff_size
番目までの要素はbuff
の1番目からbuff_size - frame_size
番目までの要素に置き換えられ,1番目からframe_size
番目までの要素はflip(u)
に置き換えられる.
上記の処理を追加するとコードは以下のようになる.
function y = fcn(u)
%buffer sizeの定義
buff_size = 100000;
%frame sizeの定義
frame_size = length(u);
%永続変数としてbuffを定義
persistent buff
%buffの初期化
if isempty(buff)
buff = zeros(buff_size,1);
end
%buffをframe_size分動かす
buff(frame_size+1:buff_size)=buff(1:buff_size-frame_size);
%現在の入力信号をbuffの先頭に保存
buff(1:frame_size)=flip(u);
y = u;
##Bufferからのデータの取り出し
buff
にデータを保存する処理を実装したら,次はbuff
に保存された音を取り出す処理を実装する.
音を取り出す際には,何サンプル前の音を取り出すのかを指定する必要がある.そのために,パラメータt
を追加する.
取り出す音声の長さはframe_size
と一致しなければならない.したがって,buff
のt + 1
番目の要素から,t+frame_size
番目の要素までを取り出す.buff
に保存する際にflip()
してあるので,取り出す際にもflip()
して元に戻す.取り出した配列は出力y
に代入する.
上記の処理操作を追加すると,コードは以下のようになる.
%パラメータtの追加
function y = fcn(u,t)
%buffer sizeの定義
buff_size = 100000;
%frame sizeの定義
frame_size = length(u);
%永続変数としてbuffを定義
persistent buff
%buffの初期化
if isempty(buff)
buff = zeros(buff_size,1);
end
%buffをframe_size分動かす
buff(frame_size+1:buff_size)=buff(1:buff_size-frame_size);
%現在の入力信号をbuffの先頭に保存
buff(1:frame_size)=flip(u);
%tサンプル前の音を取り出す
u = flip(buff(t+1:t+frame_size));
%出力
y = u;
##Delayの可視化
ここで,今実装したDelayが正常に動作しているか確認する.以下のようにブロックを組む.Delayのパラメータt
の値はConstantブロックから送信する.tは配列のインデックスを表す値なので,Constantブロックは整数でなければならない.そこで,ブロックをダブルクリックすると現れるパラメータの設定画面から,信号属性のタブにある出力データ型をint16
に設定する.tの値はKnobブロックで調整可能にする.今回は,Knobの最小値を0,最大値は今回は10000にしてある.Delay の前後の波形を確認するために,Stereo Separationの出力とDelayの出力をTime Scopeブロックに入力し,可視化する.
これを実行し,Knobをいじると波形は以下のようになる.Delayにより一定時間前の信号を得ることができている.
##ステレオ対応
現段階のディレイはモノラルの音声しか処理できない.そこで,ディレイを二つ用意してステレオの音声に対応させる.以下のようにブロックを組む.ディレイの出力をMatrix ConcatenateでまとめればLの音声とRの音声を結合し,ステレオの音声に変換できる.ディレイのコピーには右クリックでのドラッグアンドドロップが有効である.
##フィードバック処理
現段階のディレイは,遅延した音声を一度出力したらそれで終わりである.やまびこのように,何度も過去の音が鳴ることはない.しかし,一般的なディレイでは一度出力した信号を再び入力(フィードバック)することでこの処理を可能にしている.その際,フィードバックの信号は一定の割合で減衰させる.減衰処理は信号に1以下の定数を乗算することで再現できる.
入力にfeedbackの係数を表すf
を追加し,以下のようにコードを変更する.この変更は左右両方のDelayに対して行う.
%パラメータtの追加
function y = fcn(u,t,f)
%buffer sizeの定義
buff_size = 100000;
%frame sizeの定義
frame_size = length(u);
%永続変数としてbuffを定義
persistent buff
%buffの初期化
if isempty(buff)
buff = zeros(buff_size,1);
end
%buffをframe_size分動かす
buff(frame_size+1:buff_size)=buff(1:buff_size-frame_size);
%現在の入力信号をbuffの先頭に保存
buff(1:frame_size)=flip(u);
%tサンプル前の音を取り出す
u = flip(buff(t+1:t+frame_size));
%フィードバック処理
buff(1:frame_size) = buff(1:frame_size) + flip(u)*f;
%出力
y = flip(buff(t+1:t+frame_size));
次にブロックを組み替える.追加したf
も,Constantブロックから値を指定し,Knobで調整可能にする.FeedbackのKnobは最小値を0, 最大値を1にする.
##Dry / Wet の Mix
通常のDelayでは,入力信号そのもの(Dry)とDelayの出力(Wet)を混ぜ合わせることができる.Dry成分だけ鳴らすことも,DryとWetを同じ音量で混ぜることも,Wet成分だけ鳴らすこともできるのが一般的である.
ここで,Dry / Wetを混ぜるためのブロックを作る.適当な場所にMATLAB Functionブロックを置き,以下のように書き換える.
function y = fcn(dry,wet,mix)
dry = dry*min(-mix+1,1);
wet = wet*min(mix+1,1);
y = dry+wet;
dry
はDry成分,wet
はWet成分,mix
は混ぜ具合である.ここで,min(-mix+1,1)
とmin(mix+1,1)
は以下のような関数である.
dry
に掛かっている関数は,mix
が0以下で常に1,それ以上で減衰し,mix
が1の時に0となる.wet
に掛かっている関数はdry
と逆の動きをする.このような関数をdry
wet
のそれぞれに掛けることで,両者を混ぜることができる.
Dry / Wetを混ぜるブロックを作成したら,以下のようにブロックを組む.Dry成分とWet成分とmix用の定数を作成したブロックに入力している.mix
の値はKnobで調整可能にする.Knobの範囲は-1から1までとする.
Delayの実装は以上である.完成したDelayをいじると以下のようにやまびこのような効果が得られる.ギターよりもドラムの方が分かりやすいので,音源は変更してある.ダブルクリックで設定を開き,ファイルを参照すれば変更できる.
Simulinkでデジタルディレイ,さっきよりうまく実装できた pic.twitter.com/1QgvCFdVbf
— 9W3R7Y (@qwerty_16180339) December 17, 2020
#終わりに
今回はDelayを実装した.次回はVocoderを実装する.
#GitHub
以下で完成したオーディオエフェクトを公開している.
https://github.com/qwerty16180339/matlab_audio_effects
#参考文献
MathWorks, 永続関数の定義, https://jp.mathworks.com/help/matlab/ref/persistent.html