2012/10/04 (2012年10月 のアーカイブ)
続・ADSRの実装
ADSRの実装について書いたら超ニッチな話題にもかかわらず、結構いろんな人が反応してくれたので、調子に乗ってもう少し突っ込んだ解説もしてみます。
さて、ADSRはハードウェア回路としてはコンデンサの充放電によるカーブでできていて、それをソフトウェアで実現するために
現在値=目標値 + (現在値 - 目標値) × 係数
という計算でいけるよと説明しましたけど、まずこのあたりについて。
式の説明
話を簡単にするために0に向かって減少するリリースの場合だと、この式は単に現在値 = 現在値 × 係数(例えば0.99とか)
になるわけですが、こんな単純でいいんでしょうか? という話。
コンデンサの充放電って要するにRC回路の過渡応答なわけで、電気回路の教科書を探せば
V(t)=V×(1-exp(-t/RC))
なんてゆう公式が見つかるかと思います。これはRC回路に電圧Vをかけた時のコンデンサの電圧の上がり方を表しています。リリースのカーブを想定して現在値1Vで0Vに向かって減少するという場合なら、1Vを基準点(=0)と置いて-1Vで充電すると考えればよいです。つまり
V(t) = 1 + (-1× (1-exp(-t/RC))) = exp(-t/RC)
なんだ結局指数関数 exp(-t/RC) で減少してるだけじゃん、て事ですね。指数の符号がマイナスですから指数曲線の0から左に向かって動いています。
さてこれを一定周期毎に計算するわけです。つまり単位時間あたりの差分を計算するのですが、「指数の微分は指数」ですからね。言い換えれば、「今の地点の傾きは今の地点の大きさ」って事です。
つまり現在値に比例した差分を差っぴいていけば良いだけの話なので
現在値 = 現在値 × 係数
で良いのです。この式って指数関数なのですよ。
電荷がコンデンサから抜けて行くのは水の溜まった水槽の一番下に穴を開けたみたいな感じで、最初は圧があるので勢い良く減りますがだんだんゆっくりになるのをイメージすれば直感的に理解できるかも知れません。水槽に残っている水の量に比例した速さで抜けて行くわけです。
係数の話
さてじゃあ、この係数はどうやって決めれば良いの? という話。結構な頻度で繰り返し掛け算していくので 0.99... みたいな1に近い値が必要である事はわかると思います。
Attack Time とか Release Timeとか時間で指定したい所ですが、漸近してゆくだけでいつまで経っても目標値にはなりませんので、例えば「目標値の95%に近づくまでの時間」のような決め方をします。
つまり 1.0 => 0.05 になるまでの時間。仮に1mSec周期で計算してReleaseTime を1秒にしたいなら 1000回掛け算をして 0.05になるわけですから、これは
「0.05 の1000乗根」
です。C言語なら pow(0.05, 1.0/1000) とかいう感じで計算できますね。
更に更に、この辺の人間の感覚は対数的なのでツマミの反応カーブ自体が指数的(対数目盛り)であって欲しいですね。ハードウェアで作るならボリュームにAカーブを使う所です。ツマミのx=0.0~1.0に対して、最速10mSec~最長10Secとか。これは pow(1000,x)*0.01 (sec) という感じですね。
毎秒n回計算するとしてこれを上の式と一緒にすると
係数=pow(0.05, 1.0 / (pow(1000, x) * 0.01 * n))
こんな感じになります。 結構重そうだけど、係数を計算するのはツマミをいじった時だけですのであまり問題にはならないですね。
でましたデノーマル
こんな感じでADSRは実装できるのですが・・・ただし!! ここで1つだけ気をつけなければいけない事があります。それが「デノーマルの罠」です。 「現在値 = 現在値 × 係数」 で計算を繰り返していくと、だんだん0に近づいていって、これを放置しておくと「デノーマル数」という厄介な問題に突入してパフォーマンスがガタ落ちになります。詳しくは「VSTiの作り方」の方にも書いてますので参照して欲しいのですが、ほぼ0になったらどこかで計算を打ち切ってやる必要があります。
http://www.g200kg.com/jp/docs/makingvst/03.html#3.3
Javascriptで書いてると見落とし勝ちですが、先日確認した所ではこの問題はNativeだけじゃなくてJavascriptでも同じように発生するので要注意です。
Posted by g200kg : 2012/10/04 06:57:01