2017年1月28日土曜日

5. Introduction

フィルターはDSPアプリの重要なツールです。
そしてフィジカルモデリングでの中心的な役割であり,サウンドマニピュレーションとなります。

これまでに信号をスムーズに処理するために用いたSmoothや「Universal Pitch Tracker(2.2.4)」で解析箇所に用いられたハイパスフィルター,ローパスフィルターなどの多くのフィルターを見てきました。

これから他にもサウンドを加工するために用いるフィルターを見ていきます。
例えば「Auto-Wha」です。そこで,不正確な箇所を解決するためにはどうするかを5.1で見ていきます。
また,より直接的な使い方として「Signal Sideband Modulation」を5.2で扱います。

さらに「Adaptive FM Synthesis」を扱うことで,ヘテロダイニングフィルターを5.3で学びます。
最後には,5.4にてフィジカルモデリングを「circular spazialisator」で扱います。

そして,いつものように5.5において計算コストを考え,5.6で簡潔なまとめをおこない,この章の終わりとします。

2017年1月15日日曜日

2.2.4 Complex sounds Pitch Tracker

複雑な信号のピッチ判定


Sin波だけのピッチ判定については先ほど紹介した通りです。
次に,複数の波が重なった音に対して,この処理を行ってみましょう。

Fig.2.9.をみてください。


[Fig.2.9.]

100Hzの3和音の信号です。
(2つの段階が表れることが分かります)
どの段階でも,波形が上昇する部分で,
3つの0の値を持つことが分かります。
それぞれの0の値の箇所にギリシャ文字をふっています。

もし,この波形を先のピッチトラッカーで分析したなら,
3つの重要なゼロの値をどの段階でも判定するでしょう。
よって,計算結果は,100Hzではなく,300Hzになるはずです。

解決方法として,ローパスフィルターを用いることが挙げられます。
このフィルターは波形を分析して,
周波数を遮断しますが,これがピッチを判定する手法として使えます。

例えば,「first-order Butterworth」を使ってみます。
このアルゴリズムを,今回の波形に用いてみましょう。


[1st step]


まず,先の信号を分析して,だいたい300Hzだということを計算しておきます。
フィルターを適用することで,次のように波形を遮断できます。
Fig.2.10.をみてください。


[Figure 2.10: First step:]
100Hzの3和音の波形に,300Hzのローパスフィルターを当てたところ,
重要な0の値はたった2つになりました。
フィルターのおかげで波形が滑らかになったことがわかります。

[2nd step]

滑らかになった新しい波形を再び Pitch Tracker で分析します。
フィルターの滑らかにする効果によって,
0の値は排除されていきます。

そして新しく返ってきた値は,
全体の数に依存しますが,
200Hz付近になるはずです(正確には200Hzです)。

次に,ローパスフィルターをこの新しい値にセットして,周波数をカットします。
Fig.2.11.をみてください。


[Figure 2.11: Second step:]
100Hzの3和音の波形を,200Hzのローパスフィルターに通した結果です。
重要な0の値はたった1つだけになりました。

[3rd step]

重要な0の値はそれぞれの段階で1つだけになりました。
よって, sinusoid pitch tracker は正しい値を返すことになります。

しかしながら,
安定した結果を計算するためには,もう1つ試行が必要になります。
ローパスフィルターに100Hzをセットして,波形を編集します。

これで,これ以上0の値が変わらないことが明らかです。
そして, pitch tracker は正しい周波数値を返し続けることができます。
Fig.2.12.を見てください。


[Figure 2.12: Third step: ]
100Hzのローパスフィルターで,100Hzの3和音の波形を分析した結果です。
安定した結果を出力するようになります。
(次の段階は,信号が同じである限り変わらないということになります)


[Timing overview. ]


このように,すでに2つのステップを経ることで,
pitch trackerは,毎回の計算で値を更新していき,
正しいピッチを判定することができるとわかりました。
この判別は 0.019 * a 秒以下で実行できます。

実際に,最初に何もフィルターを通さなかった場合の解析区域の時間は 1/300秒でした。
よって, a/300 秒後に値が返ってくることになります。

2つ目のステップはだいたい1/200秒です。
よって,値が返ってくるのは a/200 秒後になります。

そして,値を決定する3つめの段階は1/100秒です。
よって,値が返されるのは a / 100 秒後です。

これら全ての時間を合計したら,
どれくらいかかるか,わかると思います。

これは,そんなに早い処理ではありません。

実際,aの値は10ほどになるでしょう。
(2.2.5.をみてください)
そうすると,処理にかかる時間はだいたい0.2秒ぐらいだと分かります。
それは,75BPMの16分音符の間隔です。
音楽ではよくある間隔になります。


本当に実行されるコードは,
あなたがみてきたように,
Sinusoid Pitch Tracker が解析を始める前に,
100Hzの周波数をカットオフしたフィルターが適応されます。

よって,実際に実行する際には,
たった 0.01 * a秒で解析されるだろうと考えられます。
これは先ほど計算した時間の半分です。


[Changing signal. ]


では,信号のピッチが変化するとどうなるでしょうか。
もしも,下がった場合,
同じように少しの計算を経て,低い値を検出できるでしょう。


もし,ピッチが上がった場合,
新しい波形の基本周波数が,カットオフする周波数よりも高いため,
フィルターは基本周波数の波形を遮断してしまうのではないでしょうか。

しかし,実際には,フィルターはそうは働かず,
信号の”Fundamental’s zero-crossing rate”情報には影響しません。

例のFig.2.13.をみてください。


[Figure 2.13: ]
入力された信号により,ピッチが上がりました。
3和音の波が今200Hzになっています。
そして,ローパスフィルターは古い100Hzの値にセットされています。
情報に損失はなく「sinusoid pitch tracker」は新しい基本周波数を捉えることができています。


[Faust code.]

FAUSTでのこの新しい”pitch tracker”のコードは,
“sinusoid”のものと比較して,
[proces]の定義とライブラリの追加があるだけです。


====================

import("math.lib");
import("filter.lib");
SH(trig,x) = (*(1 - trig) + x * trig) ~ _;
a = hslider("n cycles", 1, 1, 100, 1);

Pitch(a,x) = a * SR / max(M, 1) - a * SR * ( M == 0)
with {
  U = (x' < 0) & ( x >= 0);
  V = +(U) ~ %(int(a));
  W = U & (V == a);
  N = (+(1) : *(1 - W)) ~_;
  M = SH(N == 0, N' + 1);
  };
process=dcblockerat(80) : (lowpass1 : Pitch(a)) ~ max(100);

====================


“filter.lib”ライブラリを,フィルターを用いるために定義し,付け加えています。
その後,[process]の定義において,まず[dcblockerat]を呼び出しました。
これは,ハイパスフィルターで,カットオフする値を80Hzに指定しています。

これはDCオフセットの信号を除去し,
ピッチ判定の影響となるノイズを除去するために機能します。

その後,用いている[lowpass1]フィルターは,
バターワースフィルタのローパスフィルターのことです。

これは[dcblockeart]から信号を受け取り,
Pitch関数から周波数を除去しています。

また[~]演算子を付け加えることで,
入力された値を100の値を比較して選択された値を,
ピッチ関数は max関数を経て値を出力します。

適用したフィルターはカットオフした100Hz以下の値を受け取らないということに気をつけて下さい。

これが動作すること使いやすくなります。
フィルターの初期値は0Hzにもかかわらず,
値がこれに到達する前に,直接出力することができるからです。

最後にSVGダイアグラムをFig.2.14.をみて確認してください。



[Figure 2.14:]
“Universal Pitch Tracker”の[process]のブロックダイアグラムです。


筆者注:
1)OS X 10.11で試しているのですが,lowpass1が見つからないとのエラーがでます。
  別環境で動作確認する必要あり。
  ERROR : undefined symbol : lowpass1
  2行目で"filter.lib"を読み込んでいるはずなのですが。

2)OSX 10.12ではprocess自体が見つからないとのエラーになります。
  対処法がわかり次第更新します。

2017年1月12日木曜日

2.2.3 How to set “a” inside a code

Faustのバージョンが0.9.9.4j-par以上のものを用いているか,注意してください。
もし,コード内に定数を入力した際に,
“Pitch Tracker”は正しく動きません。
常に0を返すと思います。

生成されたC++のコードを見れば,その理由がわかると思います。
先に用いたPitch Trackerのコードを次のように修正しました。


process = Pitch(8), Pitch(a);


この”Process”は,2つのPitch関数を並行して計算します。
1つめは引数に8をとり,2つ目はスライダーの値を引数に持ちます。

そうすると,2つ目の関数だけが正しく動作します。
図2.7にC++が生成したコードを掲載しました。

for ( int i = 0; i < count; i++){
    […]
    output0[i] = (fSamplingFreq * (( 8 / max(iRec0[0], 1)) - (8 *(iRec0[0] === 0))));
    […]
    output1[i] = (fSlow2 * ((1.0f / max(iRec3[0], 1)) - (iRec3[0] == 0)));
    […]
}


Forループの中で,サンプリングレートが計算に使われているのが分かります。

コード内には2つのoutputがあります。
1つ目がoutput0[1]で,これは Pitch(8) の結果に相当します。
(8をコード内に見つけられるでしょうか?)

output1[i]は2つ目のoutputで,これが,Pitch(a)の結果を表しています。
これだけが,正しく動くものです。

1.0fという数値がその理由です。
この箇所は,output0のコードの8の部分を置き換えたところになります。

C++では,1.0fは浮動小数点を表します。
よって,全ての計算結果は浮動小数点になります。
(それが正しい結果です。)

1行目は反対に,整数値(int)で扱われています。
そのため,出力される結果が間違ってしまうのです。

また,今回は8の値を置き換えましたが,
Faustの関数では,浮動小数点の係数は1.0と指定するだけで十分です。
よって,単純に8.0と入力しても,最初の関数は正しく動きます。

process = Pitch(8 * 1.0), Pitch(a);
process = Pitch(8 : float), Pitch(a);
process = Pitch(8.0), Pitch(a);

これらの行は全て,同じ,正しいC++のコードを生成します。

for ( int i = 0; i < count; i++){
    […]
    output0[i] = (fSamplingFreq * (( 8.0f / max(iRec0[0], 1)) - (8.0f * (iRec0[0] === 0))));
    […]
    output1[i] = (fSlow2 * ((1.0f / max(iRec3[0], 1)) - (iRec3[0] === 0 )));
    […]
}

Figure 2.8:
  正しく生成されたC++のコード。
  両方の出力が正しく動作しています。
  1つ目のoutputが8の値の代わりに8.0f(浮動小数点)を用いているのがわかると思います。

この注意点は,3.5で扱うAdaptive FM synthesis でまた出てきます。