2016年5月22日日曜日

2.2.1 Sinusoid Pitch Tracker - one period measurement

では、周波数解析について、
まずはシンプルな機能を持つものを作っていきましょう。
後々には、より長い周期の解析もできるようになるはずです。

-----

U 関数

まずこれらの機能を作るにあたって、
0の値をつきとめる必要があります。

より最適なものを選択するために、
信号が上昇している部分の0の値だけを取得します。
(全ての0ではありません)

サイン波では、
波が上昇を描く際には2つの0の値をとります。

この関数を U ( x)関数と呼びます。
xt はt 地点での信号を表します。

-----





最初に書いた、 xt >= 0 and not x= 0 という状況に注意してください。
別々の領域(状態)で、この関数がうまく動くようにしていかなければなりません。
xt = 0 のときに、即座に正確な値を取得できる保証はありません。

このように、2つの正負に位置する場合それぞれにて値を探すことで、
この間に、少なくともひとつの0の値が生じることがわかるでしょう。


この U 関数を FAUSTでは次のように表せます。




 ' は1サンプル遅れているところを表しています。
よって、U(x)が U t (x) ならば、
x' は xt-1 になります。

& 演算子は論理ANDのことで、
2つの状態が True(1)ならば、1( True )を返し、
そうでなければ、0( False ) を返します。

U(xt)関数については、Fig.2.3. のグラフや
Fig.2.4.の .svgブロックダイアグラムからもわかるでしょう。



Fig. 2.3.
100Hzのサイン波(青線)のとき、赤の線がU(xt)関数の値です。
横軸は時間軸を表しています。
U(xt)関数が1の値をとる間に、
サイン波が1周期を刻んでいることがわかります。

SVGブロックダイアグラム(Fig.2.4.)





-----


N 関数


U(xt)関数が2つの1の値を刻む間に、
どれくらいの時間がかかるか測ることに、
興味が持てたと思います。

よって、次はその時間をカウントする関数が必要ですね。

もちろん、デジタル値で、サンプル数を数えていくことになります。

この関数を N ( xt) 関数と呼び、
もし、 U (tx) = 1 ならば、0 の値をとり、
そうでなければ、値は増え続けます。

このようにして、
U(tx)が最後に1を刻んだ後のサンプル数を数えていきます。





Fig.2.5

U(xt) 関数が赤を示し、緑線はそれぞれの N(xt)関数を示しています。
横軸は時間軸(サンプル数)を表し、縦軸は取得したサンプル数を表しています。

U(xt)関数が2つの1の値をとる間の時間は、
0になる直前のN(xt)の値に1を加算することで、
読み取ることができます。


N (xt) 関数を、FAUST では、
U(xt) が1となるときに0となる値を掛け合わせた、
普通のカウンターとして定義しています。


SVG ブロックダイアグラム




M 関数


1周期のサンプル数をカウントすることの最初の課題は、
どのようにして、N(x) = 0となる前の値のN(x)に1を加算するかということです。

この問題は、 S & H オブジェクトを用いれば簡単にクリアできます。

Triggerが N(xt) = 0 のときに、
サンプルを行う関数が N(x t-1) + 1 を取得するようにイベントを設定しておけば、
前の値のN(x)の値に1を加算した数を取得できます。

よって、もし、 N(x t) = 0 なら、
次にNの値が 0 の値をとるまでN ( xt - 1 ) の値を、保持させておけば良いのです。

この新しい関数を M (x t )関数と呼びます。



 SH関数の最初の引数は トリガーになっているということを思い出してください。
そして二つ目の引数は、取得・保持される信号になります。

トリガーの引数には == 演算子を用いました。
これは、 N (x t ) =0となったときに1を返し、
それ以外のときには、0を返すというものです。


Pitch 関数


さて、それでは、入力した信号を M(x t)関数に入れてみましょう。
もし代わりに、周波数を取得したいならば、
サンプリングレートを M(x t )の値で割りましょう。


SR は保持しているサンプリングレートを返す関数です。
そしてこれは、math.lib ライブラリをインポートすることで使用できます。

0で割ることを避けるために、
例えば、 max ( M(xt), 1)のように
分母に max 関数を用いています。

よって、M(xt)=0 のときでも、
max関数は1の値を選んで返してきます。
(この場合では、1 > M(xt) となるので)

そうすることで、分数はエラーなく定義することができます。

また、これは演算を始めたときにのみ必要なことですが、
値を初期化しておくために、
Pitch(xt) 関数の値をSR/1のままにしておくより、
null としておきましょう。

そのため、もし、M(x t) = 0 ならば、 SRを引くようにしておけば大丈夫です。

結果として FAUSTのコードは次のようになります。



コード全体


では最後に Sinusolid Pitch Tracker のソースコードを見てみましょう。
次の.dsp コードを実行してみてください。


import("math.lib");
SH(trig,x) = (*(1 - trig) + x * trig) ~ _;

U(x) = (x' < 0) & (x >= 0);
N(x) = (+(1) : *(1 - U(x))) ~ _;
M(x) = SH(N(x) == 0, N(x)' + 1);
Pitch(x) = SR / max(M(x), 1) - SR * (M(x) == 0);
process = Pitch;



process は Pitch関数を引数なしで呼び出しています。
必要とされる x 引数は、入力信号の値になります。

では、同じコードを新しい構文で書いてみましょう。


import("math.lib");

SH(trig,x) = (*(1 - trig) + x * trig) ~_;
Pitch(x)=SR/max(M,1)-SR*(M==0)
with{
  U = (x' < 0) & (x >= 0);
  N = (+(1) : *(1 - U)) ~_;
  M = SH(N == 0, N' + 1);
};
process = Pitch;



違いに気づいたでしょうか。
今回は  with { ... } という構文を用いました。

主要な関数を定義した後、セミコロンを文末に書かず、
with {...} 構文を用い、そしてその構文の最後に、
セミコロンを書きました。


with {... }構文の中には、
必要とする全ての他の関数を定義しています。
またその関数は、以前のコードのように
U(x)  といった引数を取らず、 U だけ宣言するといった書き方をしています。

with を用いた書き方には2つの利点があります。

1つは、メイン関数の引数をwith 構文の中に引き継がせることができ、
with 構文内では毎回引数に用いる必要がなくなることです。

もう1つは、with内部で宣言された関数は、
withの外では宣言されていないということです。
よって、ユーザーは同じ名前の関数を別の場所で、
構文エラーになることなく用いることができます。

0 件のコメント:

コメントを投稿