2016年5月22日日曜日

2.2.2 Sinusoid Pitch Tracker - several periods measurement

先ほど作成したsinusoid pitch trackerには、問題があります。

周波数の計算に用いた M(xt)という関数ですが、
これはサンプルごとにカウントするもので、
Time Quantization(時間量子化)のために、
明らかないくつかのエラーが存在するということです。

解決方法として、いくつかに分けられたを範囲をカウントし、
その後、この結果を想定される間隔の数で割るというものです。


幸いなことに、
普通の音(ピッチ)では、音の高さはこの小さい間隔では変化がありません。
それでは、想定される間隔の数を a として、
この値をスライダーに割り当ててみましょう。


V 関数


先ほどは、N (x) 関数を用いて、
カウンターを  U(x) = 1 というイベントが起こるたびに、
(例えば、信号が上昇して0の値になるときに)
リセットしました。

そして、 今回は、N(x t)関数を、 a U(xt ) = 0 となる後のときだけ、
0にリセットしなければなりません。

もちろん、その U(x t) = 0 イベントのために、カウンターが必要です。

このカウンターを V (x t)と呼びます。
それでは、FAUST での定義を見てみましょう。


V(a, x) = +( U (x ) ) ~ % (int(a));


カウンターの基本形は次のようになります。


count = +(1) ~ _;


+(1)  の代わりに、 + U(x)) を用いています。
これは 0 の間 1 を返します。
よって、毎サンプルごとに1増加するカウンターの代わりに、
U(x t) = 0 となるごとに1増加するカウンターを用いています。

modulo 関数(%)は、int(a)の値になったときに、
0にリセットを行う関数です。

想定した間隔のサンプリング数である a という値は、すでに整数なので、
int(値を整数にして返す) 関数は、使う価値がないように思えますが、
しかしながら、コンパイラはこの背景を知らないので、
int (a ) とはっきり述べる必要があります。


V (a, x t )関数が毎 U(x t ) = 0 となるごとに1ずつ増加する関数だとすると、
a の値に達したときに値は再び0にもどります。



W 関数


さて、私たちは、N(x t)関数の部分を、書き換えていかなければなりません。
なぜなら、 a U(x t)= 1 かつ V(a, x t )=a という状態になるまで
値が上げ続けるのを止めないようにしないといけないからです。

例えば、信号が0の値をとる間、
最後の V (a, x t) 値がリセットさせるまで、
(N (x t) 値も同じです)
それらの0の a が正しく生成されなければなりません。

新しく作る関数 はシンプルなものです。
W(a, x t)と表し、この要求された2つの状態を表しています。




FAUST では次のように表されます。


W(a,x) = (U(x) == 1) & (V(a,x) == a);




FAUSTでは真は1、偽は0が返るために、簡略化したコードは次のようになります。


W(a,x) = U(x) & (V(a,x) == a);




最終調整


さて、それでは、U(x)関数をW(a, xt)関数を用いることで、
N(xt)関数を新しくしましょう。

N(a,x) = (+(1) : *(1 - W(a,x))) ~ _;



最後に、Pitch(a, x)関数にて、
a を M(a, xt)関数で除算し、また0の時にnullとなるようにもしておきましょう。


Pitch(a,x) = a * SR / max(M(a,x),1) - a * SR * (M == 0);


コードの全体は次のようになります。



  1 import("math.lib");
  2 
  3 SH(trig,x) = (*(1 - trig) + x * trig) ~ _;
  4 a = hslider("n cycles", 1, 1, 100, 1);
  5 
  6 Pitch(a,x)= a * SR / max(M,1) - a * SR * (M==0)
  7 with {
  8   U = (x' < 0) & (x>=0);
  9   V = +(U) ~ %(int(a));
 10   W = U & (V==a);
 11   N = (+(1) : *(1-W)) ~_;
 12   M = SH(N==0, N'+1);
 13   };
 14 process = Pitch(a);


a の値を高くすれば、正確さもより増していきます。
しかし、その分、解析にかかる時間にも遅れが生じることを忘れないでください。


SVGダイアグラム図



DSP ファイル実行 



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の外では宣言されていないということです。
よって、ユーザーは同じ名前の関数を別の場所で、
構文エラーになることなく用いることができます。

2.2 Pitch tracker

'pitch tracker' のゴールはすぐに入力された信号のピッチを返すことです。
FFTやautocorrelationといった様々な手法があります。

残念ながら、FAUSTでは、これらの手法は計算することができません。
なぜなら、正確には、ユーザーがサンプルごとにそれらを計算すると、
必要でないにもかかわらず、
かなり膨大な処理コストが結果としてかかるからです。
将来的には、新しいバージョンの FAUSTではこの制限はなくなるでしょう。


現状では、つまり、処理コストの少なく、
即座に信号のzero-crossing rate を解析して、
ピッチを計算するpitch trackerを使っていくことになります。

実際、サイン曲線の波は、
1度の周期で3つの0を必ず通ります。
(1つめは最初の0で、2つめは正の値から負の値に移る際の0、
そして、最後に0を、また次の新しい波が始まる0の値をとります。)



Fig.2.2
100Hz のサイン波で、時間は横軸になります。
1周期には0.01秒かかり、
毎回3度0の値をとることがわかります。


よって、サイン曲線を用いる間、
私たちは3つの0を数え、
そして経過したサンプル数を数えればよいのです。

その後、簡単に関連するサイン波の周波数を得ることができます。

しかし、入力値がより複雑な波形だった場合には、どうでしょうか。

波形は毎周期、より多くの0の値をとるので、
より多くの0をカウントしなければならないでしょう。

信号にローパスフィルターを使えば良いかもしれませんが、
しかし、この解決方法は後にまわしましょう。


まずは、サイン波の周波数分析をする関数を作成してみましょう。

2.1 S&H


2.1 S&H

"Sample & Hold" は 、
指定されたタイミングで信号を保持出力し(sample)、
その信号を新しい指令が来るまで出力し続ける(hold)関数です。

入力された新しい信号はすぐに出力されるようになります。
これはより複雑なものを開発していくのに不可欠な機能です。

それでは FAUST のソースコードを見てみましょう。


  1 but = button("Hold!") ;
  2 SH(trig,x) = (*(1 - trig) + x * trig) ~ _;
  3 process = SH(but) ; 



UIは非常に単純です。


"Hold!" とラベルされたボタンがあるだけです。
これは `but` 変数に格納されています。

もし、ボタンが押されると、ボタンは1、または0を返します。
それらが、`but` 変数のもつ値です。

その後、`SH` 関数が宣言されています。
最初の引数である、`trig` は、イベントを始める信号です。

第二引数の x 秒の間、サンプルが続けられます。


そして、 `process` にて `SH(but)` 関数が実行されています。
独特な引数を持っていることがわかります。
(イベントを起こす信号を SH 関数では、`trig`と呼んでいます)

割り当てがない間は、待ち受け状態になります。

`but`、ボタンの値は、SH関数が信号を受け入れ始める信号を送る役割を持ちます。



SH(trig, x )関数について、もう少し説明します。

もし、 trig = 1 ならば、 SH は x  と同じ値になり、
そうでなければ、その前の値を持ちます。
(t はその時の時間を表しています。)




この関数はこのように書き表せます。



`trig` が1または0の値を持ち続ける間、
1を持っていた場合、
最初に加えられるのは空白で、そして、2つめに与えられた値を残していきます。
0ならば、逆のことを行います。

もし、`trig`に間の値をもたせたならば、2つの加えられた値は凸結合となります。
例えば、SH t-1 (trig, x) と x の間では、線形の補完が行われます。

しかし今、不幸なことに、FAUST では、この書き方は意味がなく、
ユーザーは関数をこのように呼び出すことはできません。


ですが、"~"演算子を用いて、
右の出力を左の入力に送るといったことができます。
もちろん、 1 sampleの遅れが生じます。

SH関数を書いた行を見てみると、
先に表したSH t- 1 という部分を、
同じ値で終わるものにつなげていることが想像できます。

次のような具合です。





矢印は、ループする線を表しています。
この表現は、とてもFAUST Likeで、
時間の t に注意する必要がないばかりか、
FAUST の表現に置き換えてしまいます。

このように、正確なコードに適することができます。

SH( trig, x ) = ( * ( 1 - trig ) + x * trig ) ~ _;

それでは最後に、.svg ブロックダイアグラムを見てみましょう(fig2.1)。
"Looping Cable"が、Outputのケーブルからきて、
内部の * ブロックに戻っていくのがわかります。





2016年5月21日土曜日

1. Panners


Panners

導入

この章では、FAUST で書かれた、
いくつかの Panner をみていきます。

それらは実際には用いるには適わないものもあり、
導入にのみ用いるために書かれたものもあります。

最初の2つのPanは、
Section1 と Section2 で紹介されています。

"The Simplest Panner"と、"2nd Simplest Panner"が、
それらにあたります。

そして、 Section3 ("Angle-Interpolated Pannner")にて、
実際に用いることができるくらいのPanner を学びます。
また、そこでは、Faustのライブラリのインポートの仕方も学びます。

Section 4 ("Computational Overview")では、
これらのPannerやのちに紹介するPannerの、
処理コストについて学び、
いくtかのC++のコードも説明します。

Section 5 ("Output-Interpolated Panner")では、
新しくかぎりなく正しく動くPannerを学びます。
それは、Section 4 での "Angle-Interpolated Panner"よりも、
はるかに低負荷に動きますが、
ほとんどの場合で、同じ出力結果が行われます。

Section 6("Stereo Panner") では、
2つのステレオ入力のPannerを学びます。

Angle-Interpolated Stereo Panner ( 6.1 )と、
Output-Interpolated Stereo Panner ( 6.2 )です。

最後に、簡潔なまとめを行い、
次の章への導入とします。

2. Utility objects


実用的な機能


導入


より複雑なものを作っていくためには、
様々なユーティリティを用いていく必要があります。

Sample & Hold ( S & H )や、
音声信号のZero-Crossing Rate を用いた Pitch Tracker を学んでいきます。


これらのオブジェクトは、
いくつかの段階を経ながら習得していきます。

まずは、
Single - Cycle 解析を用いたSinusolid Pitch Tracker から、
任意の信号処理を学び、
実際にアプリケーションの例をみたり、
その処理の仕方や概要について学んでいきましょう。

1.7 Conclusion

"Panner" まとめ


この章では、いかにしてpannerを作るかということを学びました。

音響の位置を左右のchのレベルを調節することで、
シミュレートすることができたと思います。


より良いシミュレートのために、
delayコントロールも用いるべきですね。

次の章では、いくつかのユーティリティを最初に学び、
その後、delay のコードを用いて、
空間音響について学んでいきます。

1.6.2 Output-Interpolated Stereo Panner


低負荷の改良型Pan (ステレオ入出力)



そしてついに、"Output-Interpolated Stereo Panner"です。

ここでは、モノラル入力だったときと同じように、
`smooth` 関数を `sqrt` 関数とともに演算させています。

  1 
  2 import("filter.lib") ;
  3 t = hslider("interpolation time", 0.001, 0, 0.01, 0.0001);
  4 c = hslider("pan", 0.5, 0, 1, 0.01);
  5 
  6 OutL(c,l,r) = sqrt(min(1, 2-2*c))*l + sqrt(max(0,1-2*c))*r;
  7 OutR(c,l,r) = sqrt(max(0, 2*c-1))*l + sqrt(min(1,2*c))*r ;
  8 pan(c,l,r) = smooth(tau2pole(t))(OutL(c,l,r)/2), smooth(tau2pole(t))(OutR(c,l,r)/2);
  9 
 10 process = pan(c);

SVGダイアグラム



1.6.1 Angle-Interpolated Stereo Panner


改良型パン(ステレオ入出力) 


次の.dspコードとそのダイアグラムをみてください。


  1 import("filter.lib") ; 
  2 t = hslider("interpolation time", 0.001, 0, 0.01, 0.0001);
  3 c = hslider("pan", 0.5, 0, 1, 0.01) : smooth(tau2pole(t));
  4 
  5 OutL(c,l,r) = sqrt(min(1,2-2*c))*l + sqrt(max(0,1-2*c))*r ; 
  6 OutR(c,l,r) = sqrt(max(0,2*c-1))*l + sqrt(min(1,2*c))*r ;
  7 pan(c,l,r) = OutL(c,l,r)/2, OutR(c,l,r)/2 ;
  8 
  9 process = pan(c);




コード内の Out L ( c, l, r)と Out R (c, l, r ) は、

もちろん出力配列になる2行を表しています。

2で割っているのは、 Out L と Out R の関数が、
2つの入力が同じチャンネル(c = 0 や c = 1)に割り当てられたときに、
音量がクリップするのを避けるためです。

SVGダイアグラム(公式のリファレンスより)







Fig.1.12
"Angle-Interpolated Stereo Panner" のダイアグラム図。
3つの段階に分かれている。

ここには、左ch のprocessingしか掲載していないが、
右ch の様子も当然ながら同じようなものになる。

実行画面



1.6 Stereo Panners

ステレオのPannner



今までみた Pan はモノラルでの入力に、
補正を加えて2つのチャンネルに出力していました。
それらを単一の入力であることから、

"Mono Panner" と呼びます。

では、どのように、2チャンネルの入力を、
2チャンネルで出力していけばよいでしょうか。

まず考え付くのは、
Pan の方向を完全に一方に振り向けた際は、
複数の入力を合わせて一方のチャンネルに送り、
もう一方を無音にしておくこと、

そして、Pan が中央にあるときには、
割り当てを変えずに、それぞれの出力にそのまま送ること、です。

これらの状態を、"Condition(1b)"と呼びましょう。

if c == 0: # Pan が左chのみ
    (S'(c))(l, r) = ( l + r, 0)

elif c ==1: # Pan が右chのみ
    (S'(c))(l, r) = ( 0, l + r)

elif c ==0.5: # Pan が中央
    (S'(c))(l, r) = (l, r)

S`(c) は新しいPanのスケーリング関数です。

変数cはPanの位置を0から1の間で表し、
(l, r) は入力信号の変数になります。
これらは、S'(c)関数の引数です。

最後に、c が間の位置になるときに平方根を用いて、出力値を調整します。
これは当然、condition(2) のためです。

出力値とPanの位置をグラフにしたものがFig.1.11です。
このグラフをみればよくわかるでしょう。

S'(c)(l,r)は、4つのグループに分けられます。


Fig.1.11
"Sterep input Pannner" にて、出力値とPanの位置の関係をグラフにしたものです。

L[x]とR[x]の線は、
左右それぞれの入力が、
左右それぞれの出力へ割り振られる値を表しています。

どちらのチャンネルから入力されているかは、
[L]と[R]という2つの引数で確認してください。

例えば、L[R]は、右chからの入力が左chに送られている量を表しています。

そしてこれは、変数cの関数になるので、
この場合だと、L[R](c)と表せます。

最終的には、
S'(c, l, r) = [...] = (OutL, OutR)
と表せ、(l, r) はそれぞれ、左右の入力値になり、
(OutL, OutR)は、出力値となります。

-----


グラフをみると、 それぞれの状態が、
"condition(1b)" であることがわかります。

なぜなら、 c=0であるとき、
つまり、Panが左チャンネルのみに割り当てられている時、
(l, r)を入力値とすると、出力値は、


Out L =  L[L] (c=0)*l + L[R](c=0)*r
          =  1*l + 1*r
          =  l + r

Out R = R[L](c=0) * l + R[R] (c=0) * r
           = 0 * l + 0 * r
           = 0

となります。

つまり、
(Out L, Out R) = ( l + r,  0)

というわけです。

同じようにすると、グラフが、

S' (c=1, l, r) = ( 0, l + r)

S' ( c=0.5, l, r) = (l, r)

となっていることがわかります。


では、次の4つの関数をみてください。


                 def
L[L](c) = min(1, 22c)
                 def
L[R](c) = max(0, 12c)
                 def
R[L](c) = max(0, 2c1)
                 def
R[R](c) = √min(1, 2c)



これらの関数の核になる部分は、
√c の部分で、
この√c が様々な形に変わって、
4つの関数に適した形に変化したものです。

min や max 関数は、
[0, 1]の範囲内に値を落とし込むのに用いる関数です。
また、負の数になるのを防いでいます。

これら4つの関数の強度の総数を足し合わせることで、
これらの関数が condition(2) であることを確かめられます。


---

より総合的なやり方として、
S'(c)関数を2x2の行列に書き換えることができ、
それをAと呼ぶと、
(l, r)の入力を積の配列に表すことができます。







それでは、これらの(2つの)関数をFAUSTで用いて、
2種類の"Angle-Interpolated Stereo Panner"と、
"Output-Interpolated panner"を作ってみましょう。

1.5 Output-Interpolated Panner


Output-Interpolated Panner



改良型 Pan と同じ動きをする、
より負荷の少ないものを作ることは難しいですが、
近似値を出す処理コストの小さい Pan を作ることはできます。


負荷を減らすためには、
`sqrt` 関数をfor ループの外に出さなければなりません。


---

改良型 Pan では、 まず `smooth` 関数を用い、
スライダーを読み込んだ後で、
2つの `sqrt` 関数を用いました。



Fig1.5 の改良型 Pan のダイアグラムを見ると、
`hslider` オブジェクトと、 `smooth`関数につながる箇所が、
すべての c ブロックの中に存在することがわかります。

`hslider` からの流れを追うと、
`sqrt` 関数を通過し、
最終的には、 ` - ` モジュールにたどりついています。


これらを言い換えると、 `smooth` 関数は、
いくつかの関数や引数と合わせて成り立っており、
その引数がサンプリングレートに影響されることからも、
`sqrt` 関数もサンプリングレートが影響していることになります。

もし、`sqrt` 関数の処理コストを、
2つ目のPan よりも少なくするなら、
`sqrt`関数を`smooth`関数の引数にしてしまう方法があります。
-----

それでは、新しい Pan のコードをみてみます。

Output-Interpolated Panner

DSPファイル


  1 import("filter.lib") ;
  2 
  3 t = hslider("interpolation time", 0.001, 0, 0.01, 0.0001);
  4 c = hslider("pan", 0.5, 0, 1, 0.01);              
  5 process = _ <: *((sqrt(1-c)) : smooth(tau2pole(t))),
  6                *((sqrt(c)) : smooth(tau2pole(t)));

C++コード


//-----------------------------------------------------
//
// Code generated with Faust 0.9.73 (http://faust.grame.fr)
//-----------------------------------------------------
/* link with  */
#include <math.h>
#ifndef FAUSTFLOAT
#define FAUSTFLOAT float
#endif  


#ifndef FAUSTCLASS 
#define FAUSTCLASS mydsp
#endif

class mydsp : public dsp {
  private:
float fConst0;
FAUSTFLOAT fslider0;
FAUSTFLOAT fslider1;
float fRec0[2];
float fRec1[2];
  public:
static void metadata(Meta* m)
m->declare("filter.lib/name", "Faust Filter Library");
m->declare("filter.lib/author", "Julius O. Smith (jos at ccrma.stanford.edu)");
m->declare("filter.lib/copyright", "Julius O. Smith III");
m->declare("filter.lib/version", "1.29");
m->declare("filter.lib/license", "STK-4.3");
m->declare("filter.lib/reference", "https://ccrma.stanford.edu/~jos/filters/");
m->declare("music.lib/name", "Music Library");
m->declare("music.lib/author", "GRAME");
m->declare("music.lib/copyright", "GRAME");
m->declare("music.lib/version", "1.0");
m->declare("music.lib/license", "LGPL with exception");
m->declare("math.lib/name", "Math Library");
m->declare("math.lib/author", "GRAME");
m->declare("math.lib/copyright", "GRAME");
m->declare("math.lib/version", "1.0");
m->declare("math.lib/license", "LGPL with exception");
}

virtual int getNumInputs() { return 1; }
virtual int getNumOutputs() { return 2; }
static void classInit(int samplingFreq) {
}
virtual void instanceInit(int samplingFreq) {
fSamplingFreq = samplingFreq;
fConst0 = (1.0f / float(min(192000, max(1, fSamplingFreq))));
fslider0 = 0.001f;
fslider1 = 0.5f;
for (int i=0; i<2; i++) fRec0[i] = 0;
for (int i=0; i<2; i++) fRec1[i] = 0;
}
virtual void init(int samplingFreq) {
classInit(samplingFreq);
instanceInit(samplingFreq);
}
virtual void buildUserInterface(UI* interface) {
interface->openVerticalBox("0x00");
interface->addHorizontalSlider("interpolation time", &fslider0, 0.001f, 0.0f, 0.01f, 0.0001f);
interface->addHorizontalSlider("pan", &fslider1, 0.5f, 0.0f, 1.0f, 0.01f);
interface->closeBox();
}
virtual void compute (int count, FAUSTFLOAT** input, FAUSTFLOAT** output) {
float fSlow0 = expf((0 - (fConst0 / float(fslider0))));
float fSlow1 = float(fslider1);
float fSlow2 = (1.0f - fSlow0);
float fSlow3 = (sqrtf((1 - fSlow1)) * fSlow2);
float fSlow4 = (sqrtf(fSlow1) * fSlow2);
FAUSTFLOAT* input0 = input[0];
FAUSTFLOAT* output0 = output[0];
FAUSTFLOAT* output1 = output[1];
for (int i=0; i<count; i++) {
float fTemp0 = (float)input0[i];
fRec0[0] = ((fSlow0 * fRec0[1]) + fSlow3);
output0[i] = (FAUSTFLOAT)(fTemp0 * fRec0[0]);
fRec1[0] = ((fSlow0 * fRec1[1]) + fSlow4);
output1[i] = (FAUSTFLOAT)(fTemp0 * fRec1[0]);
// post processing
fRec1[1] = fRec1[0];
fRec0[1] = fRec0[0];
}
}
};



SVG ダイアグラム



---

この Pan は改良型 Pan とほとんど同じ働きをするものの、
処理コストは、改良型 Pan に比べ、大幅に少なくなっています。

改良型 Pan との違いは、すべての値を S(c) 関数内部に入れてしまった部分です。


そして、出力される結果は、Condition(2) になるPan です。


改良型 Pan と比べ、 今回の"Output-Interpolated Panner"は、
`hslider`箇所が、S(c) 関数内部にあり、
補完値を出力しています。

よって、condition(2) を作り出すための計算は、
`hslider`の値を変えて、control rateを調整した際に処理が行われ、
補完値を出力する計算とは別に行われていることがわかります。

`hslider` の値を調整した際にわかる違いですが、
`Angle-Interpolated Pannder`では、常に同じ強度で聞こえていたのが、
`Output-Interpolanted Panner` では、音強度は、
`hslider`の値を変えたのちすぐに計算されるという部分です。

よって`hslider`をずっと動かし続けてしまうと、
補完部分のみしか聴こえなくなり、
元の音強度は正しく出力されません。

これはよくあるケースではありませんが、
例えば、音響実験では、
"Angled-Interpolated Panner"を用いなければなりません。

他のほとんどの音楽シーンでは、
代わりに "Output-Interpolated Panner" を用いても、
何の問題もないでしょう。


Output-Interpolated Pannerにて Pan を調整した際の特徴を Fig1.10に表しています。




緑の線は、その時間での`hslider` の値を表しています。
`hslider` は、サンプリングレート44.1KHz と
256サンプルのコントロールブロックにて、
0から1の値へ、0.1 秒かけて動いています。


よって、`hslider` の値は、毎256サンプルごとに更新され、
control block分だけ値が保持されます。
Fig.1.10では `hslider` の値は右側に書かれている値(0.0〜1.0)です。



赤色の線は、2ch の出力値の合計の平方根です。
値は一定の強度に比例し、変わるべきではありません。




しかし、この Pan では、非常にわずかな変化が起こっています。
この値は、左側の数値(1.002062 から 1.000100 pa2、大体0.0085dB)になります。


知覚するにはあまりにも小さな変化です。


Interpolative Time は 0.1 msに設定しています。
普通の Panとしてならば、非常に良好に動きます。


しかしながら、Panの値が本当に短い時間で、
2つの距離を動くならば(例えば、-1から1)、
明らかな違いがわかるでしょう。