LoginSignup
5
2

More than 3 years have passed since last update.

#02 Delay編―MATLAB & Simulinkでオーディオエフェクト

Last updated at Posted at 2021-01-02

初めに

前回に引き続きMATLAB Simulinkを用いて音声処理を行う.今回はDelayを作る.

背景知識

実装の前に前提となる知識を述べる.

Delayとは何か

Delayはその名の通り,音を遅らせるエフェクトである.やまびこのような効果を得ることができる.以下の完成版を見るのが手っ取り早い.

Delayの原理

Delayでは,前回作成したDistortionと異なり,過去の音を参照する必要がある.そのため,音を保存しておくBufferを用意する必要がある.Bufferは,初期値が全て0となる長さが有限の配列で,以下のようにところてん式に過去の音が保存される.つまり,信号が送られてくるたびに現在の音が一番初めの要素に格納され,それ以前の音は一つ後ろの要素へと移動する.尚,Bufferの長さをBuffer sizeと呼ぶ.
image.png 
図の一番上は時刻が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に名前を変更しておく.
image.png
次にStereo Separationのコードを以下のように書き換える.

function [L,R] = fcn(stereo)
    L = stereo(:,1);
    R = stereo(:,2);

Bufferの実装

以下のようにMATLAB Functionブロックを追加し,名前をDelayに変更する.
image.png
これ以降は,Delayブロックを書き換える.

まずは,Buffer sizeを表す変数buff_sizeを初期化する.今回は適当に100,000とする.フレームサイズを表す変数frame_sizelength(u)で初期化する.

次に,Bufferの配列buffを定義する.通常,関数内で定義された変数は,呼び出し毎に初期化されてしまうので,buffの定義の際にはPersistentな変数(配列)であると明示する必要がある.変数は,定義した段階では空の状態なので,isempty(buff)buffが定義された直後であることを判別できる.isempty(buff)が成り立つ場合には,buff = zeros(buff_size,1)として初期化する.

次に,buffに信号を保存する処理を実装する.この処理では信号が送られてくるたびに現在の入力信号を一番初めの要素に格納し,それ以前の音は一つ後ろの要素へと移動させればよい.ただし,現在の入力はframe_size分の長さを持った配列であることに注意すべきである.積まれた本を上から順に取り,隣に積んでいくと,本の順序は逆になる.同様に,入力の配列ubuffに追加する際には,flip()を用いてuの順番を逆にする必要がある.

また,一度の入力でframe_sizeだけbuffの要素が動くことにも注意する必要がある.この場合には,buffframe_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と一致しなければならない.したがって,bufft + 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ブロックに入力し,可視化する.
image.png
これを実行し,Knobをいじると波形は以下のようになる.Delayにより一定時間前の信号を得ることができている.
image.png

ステレオ対応

現段階のディレイはモノラルの音声しか処理できない.そこで,ディレイを二つ用意してステレオの音声に対応させる.以下のようにブロックを組む.ディレイの出力をMatrix ConcatenateでまとめればLの音声とRの音声を結合し,ステレオの音声に変換できる.ディレイのコピーには右クリックでのドラッグアンドドロップが有効である.
image.png

フィードバック処理

現段階のディレイは,遅延した音声を一度出力したらそれで終わりである.やまびこのように,何度も過去の音が鳴ることはない.しかし,一般的なディレイでは一度出力した信号を再び入力(フィードバック)することでこの処理を可能にしている.その際,フィードバックの信号は一定の割合で減衰させる.減衰処理は信号に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にする.
image.png

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)は以下のような関数である.
image.png
dryに掛かっている関数は,mixが0以下で常に1,それ以上で減衰し,mixが1の時に0となる.wetに掛かっている関数はdryと逆の動きをする.このような関数をdry wetのそれぞれに掛けることで,両者を混ぜることができる.

Dry / Wetを混ぜるブロックを作成したら,以下のようにブロックを組む.Dry成分とWet成分とmix用の定数を作成したブロックに入力している.mixの値はKnobで調整可能にする.Knobの範囲は-1から1までとする.
image.png

Delayの実装は以上である.完成したDelayをいじると以下のようにやまびこのような効果が得られる.ギターよりもドラムの方が分かりやすいので,音源は変更してある.ダブルクリックで設定を開き,ファイルを参照すれば変更できる.

終わりに

今回はDelayを実装した.次回はVocoderを実装する.

GitHub

以下で完成したオーディオエフェクトを公開している.
https://github.com/qwerty16180339/matlab_audio_effects

参考文献

MathWorks, 永続関数の定義, https://jp.mathworks.com/help/matlab/ref/persistent.html

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2