SuperCollider日記(7)

長嶋 洋一

(一部 Processing日記かも)

 Part 1   Part 2   Part 3   Part 4   Part 5   Part 6 


2011年10月8日(土)

またまた前回から1ヶ月以上が経過してしまった(^_^;)。

アルスエレクトロニカから帰国して、翌週には 2つの学会発表のハシゴ で札幌5泊→神戸1泊と国内を縦断し、その翌週には ゼミ合宿 で伊豆に行ったものの台風15号に負けて(^_^;)、止まった電車の中で9時間滞在という貴重な体験をした。 そして夏休みの残り期間には、35虎の皆んなと ジャミネータ改造大会 を行って、 「Jaminatorガールズバンド」でインカレ2011に乱入する、という計画が立ってきた。

そして9月末からいよいよ後期が始まり、まずは秋の オーブンキャンバス があり、 今年から後期に移動した芸術文化学科専門科目「現代芸術論C(メディアアーツ論)」と、 メディア造形学科の専門科目では「サウンドデザイン」に続く「サウンドデザイン演習(ハンダ付け→電子工作キット→USBキーボードの分解と改造→Gainer等でインスタ的体験、音楽理論講習)」が始まった。 これに加えて、院生ではM2の見崎さんがいよいよ修了制作のインスタ作品の追い込み、 そしてM1の2人(伊熊さん田畑さん)の「メディアデザイン特論」でのインスタ作品制作サポート、 さらに韓国からの留学生3人への特別指導もある。

ゼミでは、Comminicationゼミ(佐藤先生と和田先生と共同で担当)の、 6人の4回生の「卒業制作」と12人の3回生の「メディア造形総合演習I」が始まり、学科の企画書審査に向けてのミーティング、 個別相談と最終版企画書の提出に漕ぎ着け(再提出は1人だけ)、企画書のカラーコピー資料を学科教員に配布したところで、 前日(10/7)が終了した。 メディア造形学科の歴史は「先輩の仕事に刺激を受けた後輩の成長」であり、意欲的なプロジェクトが勢揃いした。 今から作品が楽しみである。 SuperColliderについては何も進んでいないようだが、実際にはかなりハイスピードで、あれこれやり倒してきたのである。

・・・そして今は、またまた移動中である。 浜松から京都に向かう「ひかり」の車内であり、ぼちぼち名古屋、そこからスグに京都である。 今回は出張でなくprivate旅行で、なんと30年ぶりに、学生時代に青春した京大合唱団の同級生の同窓会である。 今日はSteve Jobs(;_;)も影響されたという禅寺に予約した初めての「座禅体験」に行く。 そして帰宅日の明後日10/10にはネットワーク工事でSUACのルットワークもメイルも全て途絶消滅する。 従って、SuperCollider日記が進むこともなく、ほとんど単なるメモで終わることになるだろう(^_^;)。

さて、SuperColliderの方は、 Workshop materials for Computer Music (G6002)12. SC Programming 2 and Extensions12.2 Writing Classes.html まで終わったところだった(完全に忘却していた(^_^;))。 この次というのは、なんと 12.3 Programming (advanced).html ということになる。 前段を忘れていてアドバンスに行けるのかどうか甚だ不安だが(^_^;)、行くしかないだろう。

まずは総論である。 これまでもそうだったが、チュートリアルの記述の英文を単に翻訳するだけでなく、 一緒に僕(長嶋)の私的なコメントも記述しているので注意されたい。 SuperColliderはクラシックなオブジェクト指向言語であるSmalltalkをベースとして作られたが、 将来的な発展を想定して、CやJavaのシンタックスも含んでいる。 これは当然というか仕方ないが、時代とともに進歩発展しているオブジェクト指向の世界と同時進行して作られてきたので、 新しい(当初はこの世に存在していなかった)ものまで上位互換で取り込んでいるので、 初めて見た人にとっては、あれこれ色々なシンタックスが雑多に同居している、と思われることになる。

ここまでのチュートリアルでも、SuperColliderのクラスについての解説、またオブジェクト指向プログラミングについて解説してきた。 SuperColliderの開発者であるJames McCartney自身が書いた、ここらに関するテクニカルな議論についての記事は以下である。


James McCartney, Rethinking the Computer Music Language: SuperCollider 

Computer Music Journal, 26:4, pp. 61-68, Winter 2002. 
「プログラミング言語」全般に関する議論は、SuperColliderのhelpなどでもあちこちに記述している。 このセクションでは、さらに「プログラミング言語」の視点からの議論を進めていく。

SuperColliderの究極の目標というのは一般に、「unit generatorsのネットワークとしてサウンド楽器を構築する」ということになる。 信号処理チェーンを生み出すためには、我々は、関数(function)を記述すればいい。 この意味するところは、ReactorやMax/MSPのような「graphical unit generator languages」と違って、 SuperColliderでは完全な「algorithmic programming language」を提供できる、という事である(と書かれている(^_^;))。 なかなかに挑戦的な記述であるが、まぁ、これは事実である。 ただし、「全てが出来る」というのは、えてして、「使いにくい」のも真実である。 SuperColliderもそういうところがある。

以下は「UGen network」が関数(fubction)を生成している例である。 サウンドを生成している部分はこれ以上ない単純なサイン関数である。 その周波数パラメータの部分に、「ホワイトノイズにランダムのカットオフ周波数をかけたものを50個乗算」する、 という要素を作用させただけで、かなり複雑な こんな音がした。


(
	{
		var n, z;
		n= 50;	//you can change this
		z= 1;
		n.do(
			{
				arg i;
				z= LFNoise0.ar(
					z*(i+1), 
					rrand(0.1,2.5),
					 2.5
				);
			 }
		);
		SinOsc.ar(
			30+(100*z),
			0, 
			0.1
		) 
	}.play;
)

(ここから後は、どうやら、「SuperColliderはこんなに凄いんだぜ」という事を記述しているような気配である)
もちろん、このような信号生成/信号処理の実現と同じように、 我々は(Max/MSPのような)ユーザインターフェースであっても、 より見栄えのする複雑なグラフィカルな表現でも、プログラミングによって実現できる(と書かれている)。 ただし、これは相当に苦痛なプログラミングとなるのも事実である(^_^;)。 ・・・というところで、京都に到着し、京都御所の隣にあるバレスサイドホテルのカフェでここまで書いた。 続きはまったくアテ無しである。 これからしばらくは出張も無いので、果たしてどうなるか。(^_^;)

2011年10月10日(月)

京都3日目の朝である。同窓会part2で哲学の道を歩く、というツアーに出発するまでの30分ほどだけである。 12.3 Programming (advanced).html の続きである。

ここでのトピックは「Dynamic typing」である。 これはLive Codingとかではなくて、プログラミング言語としての変数の「型(tyoe)」についての話のようだ。 C言語であれば、


(
	double a;
	float ** f; 
)
などのように、変数を使うためには、メモリ内にその変数の領域を確保するために、まずあらかじめ「型」を宣言する必要があった。 しかしSuperColliderでは、以下のように、整数でも文字列でもポインタでもメソッドでも、何でもOKである。

(
	var a;
	a= 5;
	a= "string"; 
	a= \symbol;
	a=MouseX.new;
)
これはSuperColliderが内部的に自動でメモリ管理を行っているからである(キリッ(^_^))。 つまりリアルタイムのガベージコレクションを実現している事になり、 ポインタについて気にする必要がないという。

「class Ref」を使うことで、「Ref.new([3,4,5]);」など、我々はリファレンスを作ることができる。 これはショートカットでは「`[3,4,5];」と記述する。

関数を使う時には、以下のようにデフォルトでは「pass by value」となっている。


//passing by value

f= {|a,b| a=a+5; a+b};
(
	c=8;
	f.value(c,9).postln;
	c 		// c has not been changed by function f -
			//  its value was copied into the argument to the function
)
以下のように「passing by reference」もできる。

f= {|a,b| a.value_(a.value+5); a.value+b};
(
	c=Ref(8);
	f.value(c,9).postln;
	c //c has been changed within the function
)
「Function」クラスには、以下のような特別な機能もある。

// Variable length argument lists

(
	f= {arg ...args;  args.postln;  };
	f.value(9,10,"dave"); 
	f.value([9,2,3,\dave],7, 789,34, 456, 678);
)
SuperColliderでは、以下のように引数のダイナミックなバインディングも行う。

// Dynamic binding of arguments

(
	f= {arg freq, penguin;  [freq, penguin].postln; };

	currentEnvironment.use(
		{
			~freq= 678;
			~penguin=\chocolate;
			f.valueEnvir;
		}
	);
)
SuperColliderでは、クラスのメソッドを、以下のように名前で呼び出す事もできる。

// Calling methods of a class by name

(
	a=Array.perform(\fill, 9, {arg i; i*i});
	a.postln;
)
最後はインヘリタンス(継承)についてのコメントが、以下のようにあった。 SuperColliderでは単一の継承のみを許容しているのだという。 既に定義されているクラスにオーバーライドさせようとするとエラーになる。

For methods (functions defined in class files) single message 
dispatch is used. SC only allows single inheritance. 
Function overriding is used in derived classes but function 
overloading is never possible.

MyClass
{
	*one{arg x; }
	*one{arg x, y; }
}

* ERROR: Method Meta_MyClass::one already defined.
・・・ということで、Advancedという事だったが、このセクションは「補遺」という程度であった(^_^;)。 これで区切りとなったので、好天の秋の京都に出かけることにしよう。

2012年3月2日(金)

またまた例によって新幹線の中、それも前回から5ヶ月近く経過した2012年3月2日である。 一昨日、2月29日の午後にフト届いた1本のメイルから突然に思い立ち、 昨日3月1日の夕方に東京に行き、1泊してきた帰り(朝帰り)で、 昼前に大学に戻って、午後には入試判定の学科会議、さらに晩には3回生が企画している「追いコン」がある。

他に書く場もないのでここでメモして紹介すると、発端のメイルは川崎薫クンである。 その内容は、「高沢悟クンが東京から名古屋に行くので壮行パーティがあり、大森政人クンも来る」というものである。 突然に名前が登場したこの3人と僕は、茨大附中の同級生で、一緒にバンドをしていた。 高沢クンは東京の高校に行ったが、僕と川崎クンと大森クンはさらに水戸一高の軽音で、 一緒のバンド「SCRAP」で青春してきた仲である。 大森クンとは中学でも高校でも、バンドに加えてフォークグループも組んでいた。 言い換えれば、彼らと出会っていなかったら、一緒に音楽していなかったら、現在の長嶋洋一は、決して存在していない。 発掘してみたが、この写真の 僕の両隣が川崎クンと高沢クンであるが、ギターで写っているのは桑原クンで、この写真にも大森クンは写っていなかった。

90年代前半に一度だけ吉祥寺で川崎クンと飲んだことがあり、高沢クンとちらっとメイル交換したことはあったが、 高沢クン大森クンと会うのは、大学生の時に水戸の川又楽器のスタジオに他のバンドメンバー(桑原クン助川クン佐藤クンなど) 集まってセッションして以来、実に「32年ぶり」である。 東京で学習塾の校長をしている川崎クンがたまたま休みが取れて参加できる、ということでお誘いが来たが、 こんな機会は二度となさそうなので、入試業務の代休を出して、急遽、新幹線とホテルを予約して駆け付けたのである。

江古田のライブハウスに行ってみると、高沢クン、川崎クンなどの音楽仲間が集まっていて、なかなかに「濃い」、 素晴らしいコンサートを堪能できた。やっぱり、プログレ最強なのであった。 高沢クンは精神科医であり、名古屋に行ってからも毎週1日は東京でも診療を行う。 久しぶりの彼のタブラの演奏は良かった。 大森クンはSony Musicのスタジオの所長、まさに業界人であった。今もギターを弾いている。 川崎クンは飛び入りでピアノを弾いた、皆んな現役なのであった。 凄い刺激を受けて、元気をたくさんもらった。まさに「得がたい」友人たちである。

・・・というわけで、東京から浜松に帰る「ひかり」は1時間半しかないので、SuperColliderは思い出し程度である。 次のトピックは 12.4 Extending SuperCollider.html の「Extending SuperCollider」である。

SuperColliderのクラスライブリやUGenについては、たくさんのサードパーティから拡張されたものが提供されていて、 当然のことながらネットで全て入手できる。 例えば、「the swiki site」 (http://swiki.hfbk-hamburg.de:8888/MusicTechnology/6) とか、「the sc-plugins SourceForge project」 (http://sourceforge.net/projects/sc3-plugins/) などである。 また、GoogleでSuperColliderとかUGenとか入れると出てくるものも多い。

これらのSuperCollider拡張パッケージを、他の「help files, .sc class files and plugin files (like .scx, .sco and similar)」と 同様に使うためには、指定されたディレクトリに入れる必要がある。 Macの場合には、


/Library/Application Support/SuperCollider/Extensions/
に入れればよい。 その後にSuperColliderを起動すると、これらのライブラリは再コンパイルされて使用できる状態になる。 さらにパッケージによっては、クラスファイルを「SCClassLibrary」に、plugin filesを 「plugins directory」に、 help filesを「Help, within the main SuperCollider directory folder」に入れる必要があるかもしれないが、 たいていはパッケージにインストラクション(readme)があるので、それに従おう。

次のトピックは「Customising SuperCollider」である。 SuperColliderのデフォルトオプションを変更するには以下のようにする。


ServerOptions 

Main
しかしもっといい方法は、プラットフォームごとに用意されている「startup file」を書き換える、という、 Unixでよくある手である。 Macの場合には、以下にある。Extensions directoryに横並びである。

/Library/Application Support/SuperCollider/startup.rtf
以下は、このセクションの筆者であるNickのStartup Fileの例である。 定番のテクであるが、使用しないオプションを消さずに、 コメントアウトしてそのまま置いておくことは重要である。

//avoid Rendezvous, ServerOptions
Server.local.options.zeroConf = false;     
Server.internal.options.zeroConf = false;   
Server.local.options.numOutputBusChannels = 8;
Server.local.options.numInputBusChannels = 8;
Server.internal.options.numOutputBusChannels = 8;
Server.internal.options.numInputBusChannels = 8;
Server.local.options.memSize = 10*8192;
	//so lots of memory for delay lines in Comb UGens etc
Server.internal.options.memSize = 10*8192;
Server.local.latency=0.045; 
	//low latency is helpful for optimal performance 
	//for some machine listening processes
Server.internal.latency= 0.025;

//"SC_PLUGIN_PATH".setenv("/Volumes/data/sc3code/plugins/".standardizePath);
	// all defs in this directory will be loaded at startup
//"echo \"Loading Plugins From '$SC_PLUGIN_PATH'\"".unixCmd;
//~/scwork/synthdefs/

SynthDef.synthDefDir_(
	"/Volumes/data/sc3code/synthdefs/".standardizePath
);

//load on startup of server
"SC_SYNTHDEF_PATH".setenv(
	"/Volumes/data/sc3code/synthdefs/".standardizePath
);
	// all defs in this directory will be loaded at startup

"echo \"Loading SynthDefs From '$SC_SYNTHDEF_PATH'\"".unixCmd;

//Server.local.recSampleFormat_("int16");
	//I sometimes might use this for standard wav file out 
	//as deafult, but currently accepting the float default
//Server.internal.recSampleFormat_("int16");
Server.local.recHeaderFormat_("wav");
Server.internal.recHeaderFormat_("wav");
//Server.local.options.device = "Test";
//Server.internal.options.device = "Test";
//for general use I also use symbolic links (ln -s path) 
//from /Library/Application Support/SuperCollider/Extensions 
//to my own class folder and help folder directories

// Document.initAction_({
//arg doc;
//
//doc.background_(Color.black);
//doc.stringColor_(Color.white);
//
//});

すでに存在しているクラスメソッドや拡張クラスメソッドを、 オリジナルのソースファイルを変更することなく上書きすることは可能である。 これは、頻繁にSuperColliderをアップデートする人には、いちいち新しくダウンロードしなくてもいいので有用だろう。

筆者(Nick)は、自分のオリジナルの「Server window GUIs」にハックするために、 以下を使っているという。


+ Server {
	makeWindow {
	arg w;
	...
	}
}
この「+ Server」という部分は、「Server class」に拡張メソッドを加える、という意味である。 この対象はクラスだけでなく、インスタンスやクラスメソッドでも可能である。 もし対象のメソッドが既に存在している場合には上書きされるので、標準のGUIに対してカスタマイズできる事になる。

SuperColliderはSC3となってOpen Sourseとなっているので、もし必要であれば、 SuperColliderそのものをカスタマイズ(変更)してコンパイルする事も可能である。 しかしこれは(当然のことながら)推奨しない。 SuperColliderコミュニティとのコンパチビリティを捨てるのはちょっと勿体ないだろう(^_^;)。

SuperColliderのバージョン3.3ではさらに、「CocoaMenuItem class」というクラスが加わり(Macの場合)、 メニューバーにオリジナルのメニューを加える、というカスタマイズも可能となっている。

・・・ここらで掛川を過ぎ、もう浜松である。 しかし、このセクションが無事に終わった。 なんと次は「FFT」である。最後の山場かな。 ここは、春休みのうちに最後まで行ってみたい、という気になった。

2012年3月5日(月)

3月3日は前日の「追いコン」で飲んだための休養日、翌3月4日はSUACが電源工事で停電してネットワーク等が死滅する、 ということで、ここに「35虎」の打ち上げを計画した。 そして翌3月5日は、その回復日ということでデータバックアップ等をしていたので、SuperColliderについては夕方チラッと触れるだけ、 となった。 テキストの Workshop materials for Computer Music (G6002) は、どうやら改訂されているらしく、3月2日に進めた部分はなんと消滅していた(^_^;)。 いずれにしても、次は 12.1 FFT.html からである。 SuperColliderでFFTとは、昔のMac(1990年頃)では想像できなかった進展である。

タイトルはずばり、「FFT Processing」である。 高速フーリエ変換(Fast Fourier Transform (FFT))は、コンピュータ音楽の中核をなす技術である。 FFTによって、時間軸領域の波形の強度と、周波数領域のスペクトル(複素周波数表現のうち実数部分の周波数成分の位相と強度を表現)と、の間で有効な相互変換を行うことが出来る。 これは、スペクトル軸上での信号処理を経て、IFFT(Inverse FFT)を用いたフーリエ再合成によって時間軸領域に戻す、 という多くの信号処理を可能にする (IFFTだけでなく、サードパーティの提供する加算合成方式(additive synthesis)のUGenも使える)。

サウンドを処理するために、SuperColliderは、「PV (Phase Vocoder) UGens」を提供している。 これは一般に、


input -> FFT -> PV_UGen1 ... PV_UGenN... -> IFFT -> output
というような図式で使用する。解説はこれだけである。 そしてさらに、リンクとして

/Applications/SuperCollider/Help/UGens/FFT/FFT Overview.html
という解説HTMLがあった。 せっかくなので、こちらも ローカルに保存して、眺めてみよう。 最初のトピックは「FFT and IFFT」である。

SuperColliderはFFTをベースとした信号処理のために、たくさんのUGensを提供している。 もっともベースとなるのは、FFTとIFFTであり、これはデータを時間と周波数の領域で変換する。


FFT(buffer, input)

IFFT(buffer)
「FFT」は、スペクトルデータをローカルのBufferに、以下の順序で格納する。

DC, nyquist, real 1f, imag 1f, real 2f, imag 2f, ... real (N-1)f, imag (N-1)f
ここで「f」はウインドウサイズに関係した周波数であり、「N」 = 「window size / 2」である。

Bufferのサイズは「2の累乗」でなければならず、さらにSuperColliderのブロックサイズの整数倍である必要もある。 ウインドウサイズはバッファのサイズに関係し、デフォルトでウインドウのオーバーラップは2である。 FFTでもIFFTでもデフォルトでは「Sine window」を使用し、これらを組み合わせた「ハニング窓」となる。

テキストが二重構造となってしまったが(^_^;)、ここで 12.1 FFT.html の方に戻って、具体例を追いかけていこう。 最初のサンプルは「何もしない変換」(example 1: do nothing transformation)である。 これは非常に多くの処理系で最初にトライするものである。 例えば、1994年頃に、 Indyフリーウェア集 の中にある、 世界一高価なMIDIスルーボックス というのを作った。 これは、シリコングラフィクス(SGI)社製の「Indy」ワークステーション(250万円ほどする)がOS(IRIX)からMIDI処理をサポートしている、 ということで、「MIDIを受信する」→「それをMIDI送信する」というプログラムを、SGI社のライブラリのバグを解析・修復して作ったものである。 入ったMIDIをそのまま出す、という無駄な「スルーBOX」プログラムであったが、これにより、 後に多くの作品に繋がるプログラミングの「ツボ」を押さえることが出来た。 ここでの例でも、入った信号を結果としてそのまま出す、というものだが、いったんFFTして、また再度IFFTして出す、ということで、 重要なトレーニングとなるのである。

ここでのプログラムは以下であり、 こんな音がした。


s = Server.local.boot;

b = Buffer.alloc(s,1024,1); 
	//a buffer must be allocated which gives the size of the FFT; 
	//1024 sample window size in this case. The hop size is 
	//half the window by default.
(
	{
		var in, chain;
		in = WhiteNoise.ar(0.8);
		chain = FFT(b, in); 
			//go from time domain to frequency domain; 
			//note that the UGen does not appear to run at 
			//a conventional rate (no .ar or .kr); in actual fact, 
			//FFT and PV_UGens are at control rate, but only 
			//calculate when there is data to act on; IFFT is 
			//at audio rate to produce output samples

			//the output chain will always be -1 except when a 
			//block of fft data has been calculated; a trigger is 
			//then sent, essentially by returning the buffer 
			//number containing the data (i.e., b.bufnum in this case) 

		[IFFT(chain),in];
			//convert the data back to the time domain when 
			//input chain is a valid buffer number; I'm outputting 
			//in stereo with the IFFT output on the left and the 
			//original input on the right channel for comparison
	}.play(s);
)

b.free; //frees the resource

要するに、ホワイトノイズを音源として、ステレオの左右からそれぞれ、 「そのまま素通し」と、「FFTをかけてさらにIFFTをかけたもの」とを出しているのである。 遅延の影響でフランジング効果があるものの、見事な「スルー信号処理」である。

次のサンプルは「example 2 PV UGen example; spectral filtering」とあった。 ここでのプログラムは以下であり、 こんな音がした。


s = Server.local.boot;
b = Buffer.alloc(s,1024,1); 
(
	{
		var in, chain;
		in = WhiteNoise.ar(0.8);
		chain = FFT(b, in);
			//PV_BrickWall acts as a spectral filter, low pass 
			//when second argument (wipe) is -1 to 0 
			//and high pass for 0 to 1   
		chain= PV_BrickWall(chain, Line.kr(-1,1,10));
		Pan2.ar(IFFT(chain),0.0)
	}.play(s);
)

b.free; //frees the resource

これはもう立派にFFTによるサウンドである。 ちょうどアナログシンセの音源にホワイトノイズを使って、 LPFとHPFのフィルタのカットオフ周波数をEGによって移動させたスイープ音である。 これまで数値生成に使われていた「Line.kr(-1,1,10)」が、FFTをかけて周波数領域に行った信号の、 スペクトル特性として効いているわけで、これを従来の方法で作るのはかなり厄介である。

いきなりカッコイイFTTサウンドが出て来たが、ここで帰宅の時間となった。 続きは、忘れないためにも、できれば今週中にやっておきたいところである。

2012年3月6日(火)

さて、この週は「嵐の前の静けさ」というのか、この日の午後の教授会(入試判定、卒業判定)の他に、予定がまったく無い。 2012年の後半の国際会議についても、あとはSMCに応募するだけ、と比較的、追い込まれていない。 来週には、月曜日に後期入試(学科定員5人のところに110人の応募があった(^_^;))で4時間の実技監督と多数の面接、 そして間髪を入れずに卒業式である。 新学期に向けて、色々な仕込みをしている合間を縫って、FFTを進めていこう。

次のサンプルは「example 3 Multiple PV UGens across two chains!」である。 多重のフェーズボコーダ(PV)をUGensで作るサンプルで、FFTを「chain」を介して接続するものらしい。 ソースを見ると、サウンド入力「SoundIn」があったが、お仕事Macは何も繋がっていない(^_^;)。 そこで、過去のページから発掘して、以下のように、 いつものサウンドファイルを再生するようにアレンジしてみると、 こんな音がした。


s = Server.local.boot;

(
	b = Buffer.alloc(s,1024,1); 
	c = Buffer.alloc(s,1024,1); 
	d = Buffer.alloc(s,1024,1); 
	e = Buffer.read(s,"sounds/a11wlk01.wav");
)

(
	{
		var in1, in2, chain1, chain2, copychain;
		in1 = Saw.ar(440,0.8);
		in2 = PlayBuf.ar(1, e, BufRateScale.kr(e), loop: 1);
		chain1 = FFT(b, in1);
		chain2 = FFT(c, in2);
		copychain= PV_Copy(chain2, d); 
			//copy of FFT analysis of SoundIn 
		chain1 = PV_MagMul(chain1,chain2);
		copychain = PV_MagFreeze(copychain,LFNoise0.kr(10)); 
			//random spectral freeze effect, when random numbers 
			//(generated at 10 times a second) go above 0
		[0.25*IFFT(chain1),IFFT(copychain)]
	}.play(s);
)

(
	b.free; //frees the resource
	c.free;
	d.free;
	e.free;
)

上から順に追ってみると、まずFFTとそのコピー用の「b,c,d」とサウンドファイル読み込み用の「e」というバッファを用意する。 そして二つの入力「in1,in2」として、片方は鋸歯状波、もう片方はサウンドファイル再生、を指定する。 ここでchainが登場するが、これはFFTで指定される


DC, nyquist, real 1f, imag 1f, real 2f, imag 2f, ... real (N-1)f, imag (N-1)f
というパラメータをまとめて指定する変数となる。 二つの入力「in1,in2」それぞれのFFT結果が、刻々と「chain1,chain2」に指定されてバッファ「b,c」に入ることになる。 そして次に、「copychain」に、「PV_Copy()」メソッドによって、「chain2」に指定されたFFT結果がバッファ「d」にもコピーされる。 この次の

chain1 = PV_MagMul(chain1,chain2);
というのがここでのキモとなるが、chain1とchain2とで指定されたFFT結果のパラメータ同士を乗算して、 その結果を再びchain1で指定するバッファ「b」に格納する。 その一方で、

copychain = PV_MagFreeze(copychain,LFNoise0.kr(10));
によって、copychainで指定されたバッファ「d」の中身はランダムフリーズされる。 この結果が、ステレオの左右から、「ちょっと弱いオリジナル」と「ランダムフリーズしたエフェクト音」として出ているわけである。

次のサンプルは「example 4 Individually modifying spectral data using other UGens」である。 FFT結果のパラメータの一群を、上のサンプルのようにまとめて扱うだけでなく、当然ながら、個々に操作できるのだろう。 ここでは「pvcollect」を参照せよ、とあるので、ハイライトさせてHelpを引くと、 このような 解説が出て来た。 その中にもさらにサンプルがあったが、ここでは見なかったことにして続けていこう(^_^;)。 以下のようなソースで、これぞボコーダという、 こんな音がした。


s = Server.local.boot;

(
	b = Buffer.alloc(s,1024,1); 
	c = Buffer.read(s,"sounds/a11wlk01.wav"); 
)

(
	{
		var in, chain;
		in = PlayBuf.ar(1,c,BufRateScale.kr(c),loop:1);
		chain = FFT(b, in);
		chain= chain.pvcollect(
			b.numFrames,
				{
					|mag, phase, index|
						//this function gets called once for every bin 
					var noise;
					noise= LFNoise1.kr(rrand(0.5,1.1));
					[noise*mag,noise.range(-pi,pi)]
				}, 
			frombin:0, tobin:250,zeroothers:1
		);
		Pan2.ar(IFFT(chain),0.0)
	}.play(s);
)

(
	b.free; //frees the resource
	c.free;
)

これは、0から250までのフレームナンバごとに、「 |mag, phase, index| 」として定義されている、 FFT結果の強度と位相とインデックスを取り出し、ここにランダム変調している模様である。 聞き慣れたボコーダサウンドを実現できるブラックボックスとして、このサンプルは活用できそうである。

このセクションでのFFTのサンプルは、これで終わりである。 あとは「Some third party sources」として、以下が紹介されている。

最後に「Technical Note」として付記されている事項も重要なので加えておこう。 「machine listening」、つまりコンピュータのソフトウェアが「音楽を聞く(追従するために解析する)」という用途はとても多い。 自動伴奏や音楽解析の中核は「machine listening」である。 これをSuperColliderで多重に走らせる場合に、入力されるFFTを1つだけ共通に持って(いくつもの「machine listening」エージェントを走らせる 場合に、入力は同じものなのでこれはあり得る)、それを多数のUGenが参照する、というのはアリではあるが(原理的には相互には干渉しない)、 より安全のためには、それぞれが別個にFFTデータに関する情報を格納することを推奨するらしい。 「PV_Copy」というのは、まさにそのためのメソッドであり、処理の重いFFTをいちいち起動するのでなく、共通のFFTの結果を必要に応じてそれぞれ 「PV_Copy」で持つのがいいよ・・・という事である。 これは、FFTの処理を考えれば当然のことだろう。

さて、レクチャーの方のFFTは終わったが、冒頭に参照していた、二重構造のテキストのもう一方、 こちらの解説 がまだ残っていた。 レクチャーとほとんど重複するような予感もあるが、ここはFFTということで、こちらもチェックする事にしよう。 タイトルは「Phase Vocoder UGens and Spectral Processing」である。 FFTの焦点はなんといっても、スペクトル領域での信号処理なのである。

こちらの解説 「In between an FFT and an IFFT one can chain together a number of Phase Vocoder UGens (i.e. 'PV_...') to manipulate blocks of spectral data before reconversion. The process of buffering the appropriate amount of audio, windowing, conversion, overlap-add, etc. is handled for you automatically.」 の方がスッキリしているように気がするが、最初のサンプルは、ホワイトノイズを取り込んでFFTバッファに入れて、 そこにランダムの櫛型フィルタをかけてIFFT出力する、というものであった。 こんな音がした。


s = Server.local.boot;

(
	{ 
		var in, chain;
		in = WhiteNoise.ar(0.8);
		chain = FFT(LocalBuf(2048), in);
		chain = PV_RandComb(chain, 0.95, Impulse.kr(2.4)); 
		IFFT(chain);
	}.play(s);
)

これは、元々のサンプルでは


chain = PV_RandComb(chain, 0.95, Impulse.kr(0.4)); 
とあったものを、変化のスピードを上げるために「Impulse.kr(0.4)」を「Impulse.kr(2.4)」としたものである。 そして、単純にここの部分をさらにスピードアップして「Impulse.kr(20.4)」とすると、 こんな音がした。 これはFFTの本質部分とはあまり関係がないが、人間の聴覚にはこの違いこそ、本能的に注目させるのである。

PV Ugensは、読み込んだバッファに、出力の結果を上書きする。 そのために、連続的にサウンド入力を処理して出力する場合には、以下の例のように、2つのバッファを持つ。 ただし、これは馬鹿正直で「悪い(推奨しない)」例である。


s = Server.local.boot;
d = Buffer.read(s,"sounds/a11wlk01.wav");

(
	{
		var inA, chainA, inB, chainB, chain;
		inA = LFSaw.ar([100, 150], 0, 0.2);
		inB = PlayBuf.ar(1, d, BufRateScale.kr(d), loop: 1);
		chainA = FFT(LocalBuf(2048), inA);
		chainB = FFT(LocalBuf(2048), inB);
		chain = PV_MagMul(chainA, chainB); // writes into bufferA
		0.1 * IFFT(chain);
	}.play(s);
)

d.free;
それぞれのPV UGenが直前のバッファに新しい結果を上書きしているのであれば、それぞれFFTするのでなく、 以下の例でゼロ(サイレント)が証明されるように、「PV_Copy」を使うことを強く推奨する。

s = Server.local.boot;

(
	b = Buffer.alloc(s,2048,1);
	c = Buffer.alloc(s,2048,1);
)

(
	x = {
		var inA, chainA, chainB;
		inA = LFClipNoise.ar(100);
		chainA = FFT(b, inA);
		chainB = PV_Copy(chainA, c);  
		IFFT(chainA) - IFFT(chainB); // cancels to zero so silent!
	}.play(s);
)

x.free;

b.plot(\b, Rect(200, 430, 700, 300), nil, nil); 
c.plot(\c, Rect(200, 100, 700, 300), nil, nil);

[b, c].do(_.free);

ここでの注意点としては、PV UGensは必要な複素数表現/極座標表現を変換しているので、多重PV UGensのFFTにおいては、与えられた瞬間(時刻)における特定の値は得られないことが重要である。 以下の強度プロットのグラフのように、FFTの生成しているものは複雑な出力なのである。


s = Server.local.boot;

b = Buffer.alloc(s,1024);
a = { FFT(b, LFSaw.ar(4000)); 0.0 }.play;

(
	b.getn(
		0, 1024, 
			{ 
				arg buf;
				var z, x;
				z = buf.clump(2).flop;
				z = [Signal.newFrom(z[0]), Signal.newFrom(z[1])];
				x = Complex(z[0], z[1]);
				{x.magnitude.plot}.defer
			}
	)

)

a.free; b.free;

個々のFFTデータを扱うことを可能にするためには、「pvcalc,pvcalc2,pvcollect」等のメソッドを使って、「synth graph」の中で直接に行う。 以下の例は「example which uses the SequenceableCollection methods clump and flop to rearrange the order of the spectral bins」というもので、 こんな音がした。


s = Server.local.boot;
c = Buffer.read(s,"sounds/a11wlk01.wav");

(
	x = {
		var in, numFrames=2048, chain, v;
		in = PlayBuf.ar(1, c, loop: 1);
		chain = FFT(LocalBuf(numFrames), in);
		chain = chain.pvcalc(
			numFrames, {
				|mags, phases|
					/* Play with the mags and phases, then return them */
				[mags, phases].flop.clump(2).flop.flatten
			}, 
			tobin: 250
		);
		Out.ar(0, 0.5 * IFFT(chain).dup);
	}.play(s);
)

x.free; c.free;

ここでのほぼ最後のトピックは「Multichannel Expansion with FFT UGens」である。 FFT UGensを使ってマルチチャンネルに拡張する場合には注意が必要だ、という事のようである。 例えば、


chain = FFT(bufnum, {WhiteNoise.ar(0.2)}.dup);
というようなSuperColliderコードは、一見すると動くように思われるが、駄目である。 この例では、FFTは2つのFFT UGensを生じるが、バッファが共通なので上書きされてしまうのである。 これは、CPUを消費するだけで何も生み出さない無駄となる。

そこで、FFT UGensをマルチチャンネルに拡張する場合には、以下のように異なるバッファを用意する必要がある。


(
	SynthDef(
		"help-multichannel FFT", 
		{ 
			arg out=0; // bufnum is an array
			var in, chain;
			in = [SinOsc.ar(200, 0, 0.2), WhiteNoise.ar(0.2)];
			chain = FFT(LocalBuf([2048, 2048]), in); 
				// each FFT has a different buffer
				// now we can multichannel expand as normal
			chain = PV_BrickWall(chain, SinOsc.kr(-0.1));
			Out.ar(out, IFFT(chain));
		}
	).play;
)
上の例は、以下のようにglobalバッファを用いて記述することもできる。

b = {Buffer.alloc(s,2048,1)}.dup;

(
	SynthDef(
		"help-multichannel FFT", 
		{ 
			arg out=0, bufnum= #[0, 1]; 
				// bufnum is an array
			var in, chain;
			in = [SinOsc.ar(200, 0, 0.2), WhiteNoise.ar(0.2)];
			chain = FFT(bufnum, in); 
				// each FFT has a different buffer
				// now we can multichannel expand as normal
			chain = PV_BrickWall(chain, SinOsc.kr(-0.1));
			Out.ar(out, IFFT(chain));
		}
	).play(s,[\bufnum, b]);
)
UGenの複製を作る「dup」を使うと、以下のように簡単にコピーが作れる。

a = SinOsc.ar;
a.dup[1] === a

true
そこで、以下のようなコードを使うと、他に何もしなくても、モノラルの信号から簡単にステレオの信号を生成することが出来る。

IFFT(chain).dup
あとは、以下のように多数のリファレンスが紹介され、これらは全て公開されているらしい。
FFT Fast Fourier Transform
IFFT Inverse Fast Fourier Transform
PV_Add complex addition
PV_BinScramble scramble bins
PV_BinShift shift and stretch bin position
PV_BinWipe combine low and high bins from two inputs
PV_BrickWall zero bins
PV_ConformalMap complex plane attack 
PV_Copy copy an FFT buffer
PV_CopyPhase copy magnitudes and phases
PV_Diffuser random phase shifting
PV_HainsworthFoote
PV_JensenAndersen
PV_LocalMax pass bins which are a local maximum
PV_MagAbove pass bins above a threshold
PV_MagBelow pass bins below a threshold
PV_MagClip clip bins to a threshold
PV_MagFreeze freeze magnitudes
PV_MagMul multiply magnitudes
PV_MagDiv division of magnitudes
PV_MagNoise multiply magnitudes by noise
PV_MagShift shift and stretch magnitude bin position
PV_MagSmear average magnitudes across bins
PV_MagSquared square magnitudes
PV_Max maximum magnitude
PV_Min minimum magnitude
PV_Mul complex multiply
PV_PhaseShift shift phase of all bins
PV_PhaseShift270 shift phase by 270 degrees
PV_PhaseShift90 shift phase by 90 degrees
PV_RandComb pass random bins
PV_RandWipe crossfade in random bin order
PV_RectComb make gaps in spectrum
PV_RectComb2 make gaps in spectrum
UnpackFFT, PackFFT, Unpack1FFT "unpacking" components used in 
	pvcalc, pvcalc2, pvcollect (can also be used on their own)
・・・というところで、FFTのセクションも終わりである。 入試判定教授会も無事に終わり、明日の午前10時には合格発表である。 またSUACにやってくる新しい学生が、これでほとんど出揃うことになる。