第十七回 響け!大車輪!
(2006.1.30)

リバーブサウント(残響音)を作りたいと思った時に、思い浮かぶアルゴリズムといったら、、
今はもはや畳み込み(convolution)が真っ先に来るかも知れません。 サンプリングリバーブ全盛というか、今日日はいわゆるデジタルリバーブさえも一旦インパルス応答に落としてから 畳み込みエフェクトとして入力波形を処理するような有様でして、 ハードウェア資源が限られていた時分の、少ないディレイタップ数で、 ゴージャスな響きを作り出すアルゴリズムの工夫なんかは、かえってあまり詳しく知られていないカモシレマセン。
しかし、アルゴリズムによるインパルス応答の生成は、中々捨てがたい魅力があるものです。
もっとも大きな魅力の1つに、リアルタイムで響きをコントロールできるというものがあります。
リバーブ長や、密度、ルームサイズ、フィルタ、スプレッドなどなどリバーブのパラメータは多種多様で、 数あるオーディオエフェクトの中でも随一と言えると思います。
歌モノのリバーブなんかだと、歌っているときは、短めのリバーブにしておいて、フレーズの切れ目で、リバーブを長くする などといったやり方は、常套手段といえるでしょう。
サンプリングリバーブですと、まさにサンプリングなわけですから、普通は最初から最後まで、ずーっと同じ響き方となってしまいます。
中にはリアルタイムで響き方やリバーブ長を変えられるような兵(ツワモノ)リバーブも存在するようですが、 そいつなんかはむしろ昔ながらのリバーブ生成アルゴリズムを応用しているのではないかと思えてなりません。
つまりは、リアルタイムで様々な響き方を狙ってリバーブ成分を生成できることが、昔ながらのリバーブ生成アルゴリズムの 魅力と言えそうです。
今回は、このリバーブ生成アルゴリズムとして、僕が昔昔思いついていて、中々ちゃんと作り上げることが出来ていなかった一品 を紹介してみようと思います。というよりもこのコラムで紹介するために、アイデアをブラッシュアップしたと言えなくも無いです。

スクナ
省キ点ニテ厚キ響ヲ

デジタルリバーブは広い意味ではディレイエフェクトです。 なので固有のディレイタイム(ヤマビコの間隔)と、フィードバック量を持ち、ディレイ成分(ヤマビコ成分)を生成する要素(ディレイタップ)を各々組み合わせることにより、残響音を作るというのが基本となります。
昨今のマシンパワーを鑑みれば、100個とか200個とか十分なディレイタップ数を用意してあげても、大した負荷とならないようにも思えます。
しかし、響き方をリアルタイムでコントロールしたい、しかも、より複雑な計算方法で前例の無い独創的な響きを作り出したいなどといった 野望を有する場合に備えて、少ないディレイタップ数で工夫を凝らす方法を知っておくのも損ではナイ。
少ないディレイタップ数で、それなりに厚みのある響きを作る場合は、 各々のタップのディレイタイムをなるべく整数倍の関係とならないようにランダムに散らすという手がありまして、 この手は割りと重要で、もちろん外せないですが、それだけでは不十分というのか、 ディレイタップ数をある程度多くしないとディレイ臭さが残ることになります。
ところで、このディレイ臭さとは一体なんでしょうか?
物の本を読んでいると固有モードだとか色々と難しいそうなことが書いてあるものですが、 要するに一定の間隔でヤマビコのように響く感じがいかにも無機質で硬い感じの質感となってしまうし、 特にディレイタイムが短い場合は、周波数特性としても変なクセが付いてしまう(コムフィルタ効果)が気にかかるわけです。

ディレイタップ数を十分に増やして、ディレイタイムもバラバラにすれば、各々のクセが相殺されより自然なリバーブ感を得られることでしょう。
しかしここで必要としているのは、少ないディレイタップ数で、クセの無い厚みのあるリバーブを作る妙技です。
これを実現するためのほとんど唯一と言ってもいい手段は、ディレイタイムをこまめに変更することです。
しかし、そうは言っても単純にディレイタイムをランダムに変更させてしまっては、変えた瞬間に当然の如くノイズが発生してしまいますし、 仮にゆっくりと前後に移動するようにディレイタイムを変更したとしても、ドップラー効果が発生して、微妙にピッチが変わってしまいます(これを利用したエフェクトがフランジャやコーラスだったりします)、 そもそもディレイタイムを1サンプルづつ移動させる程度では十分に滑らかとは言えず、オーバーサンプリングするか、上手い補間処理を行わなければ、 聴き取れるノイズが発生してしまうか、そうでなくてもイマイチ硬い音として感じられてしまうことでしょう。。。

「大車輪」と名付けたその響き

そこで僕(と世界中の恐らく多くのDSPオタクども)が考えましたのは、「トレモロ(振幅変調)をかけることによって、音量(フィードバック量)が0のポイントを作り、そのポイントでディレイタイムをサクッと変更する」 という技です。
当然音量の変化があるのですが、ディレイタップは複数あるわけで、それらを同一周期で、しかし位相をずらすことにより、サミダレ式に音量0ポイントがやってくるようにすれば、 全体として「はほとんどトレモロがかかっているようには聴こえまい。この粉飾は99.9%バレない!!」といように考えたわけです。
図にするとこんな感じです↓

僕はこのスキームを「大車輪」と名付けました。それは今を遡ること昔、20世紀の終わり頃のことです。ちょうどその頃僕は筋肉少女帯の同名のアルバムを良く聴いていて、 何となく車輪状のトレモロ波形にディレイタップが等間隔で張り付いていて、地面に接するところでディレイタイムがサクサクっと変わっていく、、、
そんなイメージと、この言葉が妙にマッチしていたわけです。
しかしこれを器用に扱った結果として、プログラムを実際に書いたのは、つい今しがたのことです。。閑話休題

仮にディレイタップ数を30として、トレモロの周期を4Hzとすると、ディレイタイムの変更は、毎秒120回も行われることになります。しかし個々のディレイタップのトレモロ周期を見れば4Hz程度なので目立った倍音も発生しないものと思われます!!
しかし、思いついたことをそのまま実行に移しましたところ想定外の自体に発展してしまいました。。。
というのも、僕がコントロールしているのは只の音量ではなくフィードバック量だったからです。

ディレイエフェクトのフィードバック量を調整してみたことがある人なら分かると思うのですが、 50%→51%と、98%−>99%だと全然違うというか、前者はディレイ成分の減衰の速さにほとんど差は感じられないのですが、 後者だと大きく違って聴こえると思います。
要するにフィードバック量に対して、ディレイ成分の減衰時間は指数関数的に対応づいているというのか、 フィードバック量が多いときと少ない時で、残響量がまるで違っていて、 今のようにトレモロの波形にDCオフセットのかかったサイン波(0〜最大音量)を使ったりすると、最大音量付近のタイミングでしかほとんど響かないことが分かってしまったのです。。。

つまり、残響量という観点から考えてみると、

のつもりが、実は

だったわけです。

ディレイタップは残響を作り出すために存在する希少なリソースなわけですから、トレモロ波形としてDCオフセットのかかったサイン波では、全然厚みが生まれないし、まさに杜撰な手口であり不適格ということが分かってしまいました。
更に言うと、このトレモロ波形では(詳しくは後に述べますが)フィードバック量の概算も難しいのです。最大フィードバック量をある一定の値に設定した時に、トータルでどの程度の残響を作り出しているのか、見当が付かないのです。

そこで考えてみましたトレモロ波形が、コレです。↓

コード例で示すとこんな感じです。ちなみにphaseは0から1の範囲を、例えば4Hzくらいの周期で延々と繰り返す変数です。phase自体の波形は右肩上がりのノコギリ波(/|/|/|/|)ということになろうかと思います。
phase=0のところで、トレモロ波形の音量が丁度0になるようになってます。

	//0.0〜0.1、0.9〜1.0だけをcosでフェードアウトさせる
	if (phase < 0.1) {
		trem = (0.5 - cos(phase * 5 * GN_PI2) * 0.5);
	} else if (phase > 0.9) {
		phase = 1.0 - phase;
		trem = (0.5 - cos(phase * 5 * GN_PI2) * 0.5);
	} else {
		trem = 1.0; //最大振幅を維持
	}

要するにディレイタイムを変更するタイミングでだけフェードアウトして、それ以外の時間は、 最大のフィードバック量に固定しておく、というわけです。 こうすれば、トレモロ周期の中で残響を作り出す時間も十分に取れますし、フィードバック量を概算するにも、 フェードアウトする区間は例外というのか、誤差の範囲だと考えることができます。

フィードバック量の概算

さてさて、リバーブ設計の1つの肝として重要なのは、フィードバック量の概算です。
少ないディレイタップで重厚な響きを作り出すには、フィードバックに頼らざるを得ず、しかも複数のディレイタイムがランダムで存在するので、 残響時間の見積もりは至難の業であります。
リバーブタイムが上手く定まらず、場合によってはハウリングというのか、音量が無限大方面へ拡散してしまう自体に発展しかねない訳です。。
でも、躊躇していては始まらないので、とりあえず1つのディレイタップだけだということにして、考え始めてみましょう。

まずリバーブタイム(残響時間)とは何か?というとですね、実はこれは業界標準の定義があるようなのです。つまり、
入力波形が途絶えてから、60dB減衰するまでの時間。
だそうなのです。60dB減衰とは、1000分の1に減衰と言い換えることも出来ます。 これを保証するには、ディレイタイムとリバーブタイムとの比で考えることが重要です。
例えばディレイタイムが100ミリ秒で、リバーブタイムが1000ミリ秒だとしましょう。
この場合、最初にインパルスが入力されてから、リバーブタイムである1000ミリ秒後に到達するまでの間に、10回のヤマビコが発生している ことになります。 ヤバビコはある一定の比率(フィードバック量)で指数関数的に減衰していくわけです。つまり10回のヤマビコは、フィードバック量を10回掛け合わせた結果の音量になっていることになります。90%のフィードバック量だったら0.9の10乗で、0.3486784401という数字になっております。これは1000分の1(0.001)よりも大きな値ですから、0.9というフィードバック量は大き過ぎることになります。

ぶっちゃけこの課題をクリアするには以下の数式を使うことになっております。
フィードバック量=0.001^(ディレイタイム÷リバーブタイム) (^はべき乗を表わす)
今回の場合だったら、
フィードバック量=0.001^(100÷1000) = 0.001^0.1 = 約0.5012
となります。ちなみにべき乗はC言語の標準ライブライリなんかだと、pow(底,指数)という関数が用意されているようです。

あとは、ディレイタップ数がフィードバック量にどう影響するかを考えないと行けません。。。
でもこれはオイラにゃ身が重過ぎます。。。
なので、ディレイタップ数を色々と変えてみて、実験を繰り返しつつ、適当に勘でやってみましたところ・・

「ディレイタップ数の平方根で割ってみる」
と大体上手くいくようです。
合わせると、、
フィードバック量=(0.001^(ディレイタイム÷リバーブタイム))÷sqrt(ディレイタップ数) (sqrt()は平方根を表わす)
となりました。(゜▽゜A) <アハハ

あと、フィードバックの位相にも注意が必要です。プラスばかりのフィードバックだと、基本的に直流成分が優勢になるというのか、本質的にはローパスフィルタしか作れないことになっていたりして、波形が上や下にへばりついてしまいます。
逆にマイナスの位相ばかりだと、今度は、低音から削れてしまうという悩みを抱えることになります。なので、フィードバックの位相はプラスとマイナスで偏りがないように、交互に設定するなどの対応が必要となります。

確率密度は蜜の味

さてさて、ディレイエフェクトのカタマリとしてリバーブエフェクトを実現する時に、 キモイちゅーのキモイ、、、、ではなくて、肝中の肝となるのが、
ランダムに変化するディレイタイムの確率密度分布のデザインというのは言い過ぎと言えるはずがありません。
確率密度分布というと随分堅苦しく聞こえるかもしれませんが、要するに、ディレイタイムが任意の値にセットされる確率をグラフにしたものです。(やっぱ堅苦しい?)
これは言葉とグラフを対にして説明すると分かり易いかと思います。

例1

ディレイタイムは、100ミリ秒〜300ミリ秒の区間で同じ確率で分布(一様分布)し、それ以外のディレイタイムは絶対に有り得ない。

例2

ディレイタイムは、50ミリ秒〜100ミリ秒の区間で50%の確率で一様に分布し、200ミリ秒〜300ミリ秒の区間で50%の確率で一様に分布する、それ以外のディレイタイムは絶対に有り得ない。

例2で注意すべき点は50ミリ秒〜100ミリ秒の区間にディレイタイムが現れる確率と、200ミリ秒〜300ミリ秒の区間に現れる確率は、それぞれ50%づつなのに、グラフでは50ミリ秒〜100ミリ秒の区間が2倍の高さになっていることです。これは複数の区間があり、各々の区間の出現率が同じだとすると、区間が狭い方が、その区間内に存在する、ある1つのディレイタイムが選ばれる確率が高くなるという意味で考えてしまえば良いでしょう(まあ、正確にゆうと微妙に違いますけれど。。それはそれ)
この場合、同じ高さにするには50ミリ秒〜100ミリ秒の区間の出現率を33.3%、200ミリ秒〜300ミリ秒の区間を66.7%とすれば大体よいです。

これらの確率密度分布を持つディレイタイムの値をプログラムで生成するには、一定区間(例えば0.0〜1.0)で一様分布する乱数を作る関数を用意しておいて、 この関数の戻り値を抵当にスケーリングすれば良いです。実際にコード例を示しますと、、、

例1

	double noise;
		
	noise = Noise(); //0.0〜1.0で一様分布する乱数
		
	//100〜300へ移動
	noise = 100.0 + noise * 200.0;

例2

	double noise;
		
	noise = Noise(); //0.0〜1.0で一様分布する乱数
	if (noise < 0.5) {
	//50%分の区間
		noise = noise * 2.0; //先ずは0.0〜1.0の範囲に戻して。。
		//50〜100へ移動
		noise = 50.0 + noise * 50.0; 
	} else {
	//残りの50%分の区間
		noise = (noise - 0.5) * 2.0; //こちらも0.0〜1.0の範囲に戻して。。
		//200〜300へ移動
		noise = 200.0 + noise * 100.0; 
	}


/****(同じ高さにする場合) ***************************************/
	double noise;
		
	noise = Noise(); //0.0〜1.0で一様分布する乱数
	if (noise < 0.3333) {
	//33.33%分の区間
		noise = noise * / 0.3333; //先ずは0.0〜1.0の範囲に戻して。。
		//50〜100へ移動
		noise = 50.0 + noise * 50.0; 
	} else {
	//残りの66.67%分の区間
		noise = (noise - 0.3333) / 0.6667; //こちらも0.0〜1.0の範囲に戻して。。
		//200〜300へ移動
		noise = 200.0 + noise * 100.0; 
	}
といった感じです。
本当を言うとこの辺は色々な奥義の目白押しなのですが、リバーブ用のディレイタイムを設計するには、 コレくらいで何とかなると思います。興味のあるお方は、ネットで「確率変数の和」、「確率分布」とか色々調べてみて下さい。。

ディレイタイムの設計で注意すること

さてディレイタイムの確率的な割り振り方を解説したので、あとは実際に割り振る時の コツなり注意点なりを書いてみたいと思います。
単刀直入にいいますと「近すぎず、遠すぎず、濃すぎず、薄すぎず」という感じです。

「近すぎる」、つまりディレイタイムが短すぎますと、 同じ残響時間を作り出すのに必要なフィードバック量が大きくなってしまいます。 つまり必要な残響時間を作り出すために一定間隔のヤマビコがたくさんたくさん作られてしまって、 周波数特性のクセが付きやすい、、それどころか音程感さえ生んでしまう結果となってしまいます。
このクセは一兆一夕には解消できるものではありませんので、 短すぎるディレイタイムが生まれないような設計をする必要があるかと思います。
大体の目安は20ミリ秒よりも短いものはヤバイかなぁという気がします。

「遠すぎる」場合ですが、これは逆にフィードバック量が小さくなってしまって、 ディレイタップの残響生成能力を生かしきれないという問題が生じます。
つまりリバーブタイムが3秒のところ、ディレイタイムが3秒だったら、最初のヤマビコは-60dB、 次のヤマビコは-120dBとなってしまって、ほどんど聴こえないわけです。
なので、これも、、大体リバーブタイムの3分の1程度までにしておいた方がよいかなぁ

「濃すぎる」というのは、確率密度が高すぎる。先ほどの図で言うと一部のディレイタイムの区間(たとえば100ミリ秒〜101ミリ秒の区間) が異様に高くて、大半のディレイタップが密集してしまっている状態です。
こうした場合は、1つの問題として「単一のディレイのように聞こえてしまう」という問題が発生します。
しかしもっと深刻なのは、「結果としてのフィードバック量が不安定になる」問題です。
非常に短い時間区間にディレイタップが集中してしまいますと、同一サンプルに複数のディレイタップが重なる状態が高頻度に発生します。 「フィードバック量の概算」のところでも述べましたが、ディレイタップのフィードバック値の位相はプラスとマイナスが半々ずつですので、 同一サンプルに重なっているディレイタップのフィードバック量の「平均値」は0です。
しかし分散というのか、不安定さは非常に大きくなります。4つのディレイタップのフィードバック量が全てプラスで、しかも最大値で重なっていたとしたら。。想定されるフィードバック量の4倍の値となってしまい、その結果ハウリングのような状態が発生してしまいます。
つまり普段はあまり響かないけど突然ハウリングを起こして、また響かなくなる。。
というようにリバーブの概念を通り越したキワモノ系エフェクトと化してしまうのです。

「薄すぎる」を説明しましょう。この薄すぎるは、「濃すぎる」や「遠すぎる」の影響で死んでしまうディレイタップが生まれる反動として発生することが多いのですが、つまり、あるディレイタイムの区間に割り当てられるディレイタップの発生率が低すぎる状態です。
これが問題なのは明らかでしょう。ヤマビコっぽさが解消されず、「リバーブというより、複数のディレイがランダムに変化しているように聴こえる」という状態が発生してしまいます。

以上の注意点を考慮に入れておけば自然なリバーブ感がイマイチ得られないけど、どうしていいか分からないという問題は少なくなるものと思います。
ただしこれらは「自然な」リバーブ感を得るためにどうしたらいいかなぁ という僕なりの工夫に基づくものですので、 逆に極端な設定で面白い音響を作り出したい筋には、もっと色んなディレイタイムの分布を試して頂くのも 一驚かなと思います。

高域ダンピング

リバーブを自然な感じで響かせるためには、「だんだん音がぼやけて行く、目立たなくなる」という要件が満たされるとベターです。
これを実現するためには、ディレイタップのフィードバックにローパスフィルタをかけるのがベターです。
ローパスフィルタの実現方法は色々なパターンがあって、これはこれで書いていくとキリがないですが、とりあえず1次IIRフィルタというのが一番簡単な方法かと思います。要するに、、

出力波形=入力波形×α+1サンプル前の出力波形×(1−α)
というような式で、αが1だとフィルタはかからず、αが0.5だとそこそこかかって、αが0.99だとかなりかかる。 αが1.0だと音が出ない。という感じになります。
最もシンプルですが、とりあえずローパスフィルタを作りたい時はそこそこ使えます。
もっと詳しく知りたい人は、「IIRフィルタ」、とか「FIRフィルタ」とかでネットで検索してみてくださいな!

ステレオ感

大車輪リバーブレーションモデル(名前付いてるし、、)で、実は一番面倒だったはステレオ感のコントロールです。。
基本は1つ1つのディレイタップを左右で異なるフィードバック量にするのですが、、 ディレイタイムがランダムに変わるので、特にディレイタップ数が少ない場合は、定位が右に左にずれてしまう。。
ぶっちゃけこの問題は解決してないというか、、ある程度ディレイタップ数を増やすことでなんとかごマ化してます。。

お土産コーナー

今回の大車輪リバーブを使ったサンプルサウンドはコチラよりダウンロードして頂けます。
内容は以下の通り。。
1つめ原音
2つめ25個のディレイタップ
3つめ300個のディレイタップ
4つめ30個だけど短めのディレイタイム
5つめ50個だけど「短すぎ&濃すぎ」と「薄すぎ」の組み合わせ
サンプルコードはコチラよりどうぞ。

では、また来月あたりに更新できるといいなぁという願いを込めてサヨウナラ。
 

前の記事へ | 次の記事へ | 戻る