SuperCollider日記(2)

長嶋 洋一

(一部 Processing日記かも)

 Part 1   Part 3   Part 4   Part 5   Part 6  Part 7 


2011年3月13日(日)

前回の日記が3月5日で、その翌日3月6日は停電工事でストップ、 その翌日から4日間、 沖縄 に行って帰ってきた翌日、3月11日(金)は終日の学科会議だったその途中に東日本大震災が発生して会議も中断解散。 その翌日、3月12日(土)は一般後期入試で240分の実技試験監督と18人の面接試験、とヘトヘトになった。 明らかになってきた地震の被害状況に続いて福島原発の放射能漏れ、そして東日本大震災の規模がマグニチュード9.0と発表され、 日本全国が浮き足立っている中で、とにかく少しだけでも進めることにした。

前回からの引き継ぎで、 Workshop materials for Computer Music (G6002) からということになる。 まずは 1. Introduction and Overview の最初、 1.1 Getting Started.html からである。 どうやらこれは、プレゼンシートの分量のイメージで分割されたHTMLのようだ。 最初にある「'Essentials'」では、SC3について、「It is an interpreted programming language for audio synthesis. You have to learn this language:」と、目的と条件を以下のように明確にしている。

  1. The stupid computer will only accept syntactically correct statements in this language.
  2. You need to become aware of standard mechanisms of computer languages, like iteration, conditional execution, messaging and containers.
  3. It's frustrating if you have a specific musical task in mind to have to deal with this computer language stuff.
もっともな話である。(^_^;)

次の「payoff」を飛ばして、既に行っている必要条件が以下のように示されている。3番目の情報は収穫である。

  1. Use enter, not return, to run lines of code. Enter is at the base of the numeric keypad on some keyboards, on the bottom row of some older laptop keyboards, or obtain it with shift+return or ctrl+return.
  2. Before doing any sound synthesis, turn on the localhost server, by pressing 'Boot' on the grey localhost server graphic.
  3. To stop running sounds, use command+period (apple key and .)

そしていきなり、サンプルである。これはビギナーのモチベーションのためには重要だ。 ただし、localhostサーバの起動はノータッチである(^_^;)。 最初のサンプルは以下のようにたった1行である。


"I am SuperCollider 3".speak //run with enter

これを実行すると、合成音声で「I am SuperCollider 3.」と読み上げられた。 ただし、「record」ボタンでもこのサウンドは録音されなかった。 「speak」と「play」では、サウンド出力の形態が違うらしい。

2番目のサンプルは以下のサイン波である。 これはあまりに予想がつくので詳細は省略。


{Pan2.ar(SinOsc.ar(440,0,0.1),0.0)}.play

そして3番目のサンプルは以下である。 マウスのX座標で音色がこんな変化をした。


{Pan2.ar(SinOsc.ar(MouseX.kr(440,880),0,0.1),0.0)}.play 

その次のサンプルとしては、「(」と「)」で囲まれた全部をエンターキーで叩く、というものであった。 以下のようなプログラムであり、画面内のマウスカーソルのX座標とY座標に対応して、ミックスしているサウンドのバランスと、フィルタのカットオフ周波数をインタラクティブに制御できる。 こんな音がした。


(
	{
		var n;
		n=34;
		Resonz.ar(
			Mix.arFill(
				n,
				{
					var freq, numcps;
					freq= rrand(50,560.3);
					numcps= rrand(2,20);
					Pan2.ar(
						Gendy1.ar(
							6.rand,6.rand,1.0.rand,1.0.rand,
							freq ,freq, 
							1.0.rand, 1.0.rand, 
							numcps, 
							SinOsc.kr(
								exprand(0.02,0.2),
								0,
								numcps/2,
								numcps/2
							),
							0.5/(n.sqrt)
						),
						1.0.rand2
					)
				}
			)
			,MouseX.kr(100,2000),
			MouseY.kr(0.01,1.0)
		);
	}.play
)

これに続くシートでは、Max/MSPやPdとの関係について述べているが、パス(^_^;)。 その次のシートでは、コメントの入れ方が以下のように紹介されていた。


//this is a comment


/* this is also a comment */

次のシートでは、以下のサンプルで、SC3がUGenを起動する、という紹介があった。 音は出ないものの、


{Pan2.ar(SinOsc.ar(Saw.ar(10),0,0.1),0.0)}.play
のプログラムを実行させると、

の画面が

のようになった。よく見ると、localhost serverのCPU値とかUGen数とかがちゃんと変化している。 次の例は、再びブラケット全体のエンター、という例である。 こんな音がした。


(
	//select this code within the outer parentheses 
	//then press the ENTER key on the numeric keypad
	{
		SinOsc.ar(440,0,0.1)
		+
		Pulse.ar(443,0.6,0.05)
	}.play
)

つぎのトピックは、以下のようにカッコの種類である。


Types of Parentheses 

(  )  //for grouping expressions together

{  }  //function

[  ]  //array (list of data)

カッコは以下の例のようにネスティングできる。


(
	if(4==4, {
		if(3==3, {
			"correct!".postln
		});
	});
)

correct!

次のトピ゜ックはキーボードショートカットだったが、ヘルプを引くという「Cmd+d」は出てこなかった(^_^;)。 ただし、ソースコードを引く「Cmd+j」は、出た。 postウインドウで「LFSaw」をハイライトして「Cmd+j」すると、 「POsc.sc」というウインドウが出て、以下のように該当部分が見れた。


LFSaw : UGen {
	*ar {
		arg freq = 440.0, iphase = 0.0, mul = 1.0, add = 0.0;
		^this.multiNew('audio', freq, iphase).madd(mul, add)
	}
	*kr {
		arg freq = 440.0, iphase = 0.0, mul = 1.0, add = 0.0;
		^this.multiNew('control', freq, iphase).madd(mul, add)
	}
}

次のトピックは「postln」によるデバッグである(省略)。 その次の例は「以下の例はクラッシュするが気にするな」(^_^;)であった。


This will crash:

{SinOsc.ar(nil)}.play

So will this:

Array.series(9,0,1)+nil

Don't be scared!

確かに、クラッシュと言っても、エラーがぞろぞろ出るだけだった(^_^;)。 だいたいこんなところで、このレクチャーの「Getting Started」は終わりである。 とりあえず、ちょっとだけでも進んだ、というところで今日はオシマイ。

2011年3月18日(金)

前回の日記が3月13日で、翌14-15日は風邪ダウンでメディア造形の追いコンも欠席、抗生物質と多量の睡眠でなんとか回復。 翌3月16日(水)は 卒業式 で午後はずっとホテルオークラ内。 その翌日、3月17日(木)は大震災のため後期追加入試で8人の面接試験があった。 福島第一原発 の状況もあり、ほとんど進めない1週間となった。週末は祝日と3連休なので、ここで挽回したい。

前回からの引き継ぎで、 Workshop materials for Computer Music (G6002) の次の項目、 2. Sound Synthesis からである。 その最初は、もっとも基本的な楽音合成として Sound Synthesis in SuperCollider (wk2, subtractive and additive synthesis) である。

まずはお約束で環境の起動である。とりあえずはステレオじゃなくてモノラル(左だけ)の音が出るという。 最初はサウンドサーバ(localhost server)でなくてinternal serverを以下のように起動してscopeで、という事だが、何故か今回も起動しなかった。 まぁ、起動しないものは無視して進めていくしかない(^_^;)。


Server.default=s=Server.internal;

s.boot;
楽音合成の冒頭は「Unit Generator」であるが、サンプルは無くて文章だけであった。 Unit Generatorは、CsoundでもMax/MSPでもPdでもReakorでも基本中の基本である。 まぁ、解説は「There are many primitive building blocks, like types of tone generator, filter or spatialiser, that are the unit generators. These are connected together in a processing graph to make more complicated synthesisers and sound processors. These primitives are referred to as UGens. Each UGen has some set of inputs and outputs. Most UGens have just one output, an audio stream or some sort of control signal. The inputs vary a lot depending on the function of the UGen.」ということで次に進もう。

電子楽器の音源の歴史と同様に、次の楽音合成は「減算方式(Subtractive Synthesis)」である。 元信号として広域成分を含んだ波形を使って、ここにLPF系のフィルタをかまして音色などを形成するというもので、 ミニムーグなどアナログシンセもここから始まった。 詳しくは、ちょうど、静岡県工業技術センターの人が絶版の著書をPDF化してくれたので、 コンピュータサウンドの世界 を参照されたい。

scopeは聞かないのでサンプルをちょっと改訂してみると、最初の例は以下のホワイトノイズである。 こんな音である。


{WhiteNoise.ar(0.1)}.play

このホワイトノイズにLPFをかける、というサンプルが以下である。 確かにカットオフ1000Hzで高域が減衰して音が丸くなった。 こんな音である。


{WhiteNoise.ar(0.1)}.play

このLPFは以下のような書式でUGenなど色々な入力に対するフィルタを実現する。


LPF.ar(input signal, cutoff frequency, ... )
上の例で1000Hzだったカットオフ周波数を時間的に動かすことで、タイムバリアントな面白い音色が出来ることになる。 ここには、Ma/MSPにもあった「line」「line~」と同じように「Line.kr」というのがあり、以下のような書式である。

Line.kr(10000,1000,10) // take ten seconds to go from 10000 to 1000
そこで、上の例のホワイトノイズにLPFをかけるサンプルのカットオフ周波数を10000Hzから1000Hzまで10秒間かけてリニアに下げる、というのは以下のようになる。 こんな音である。

{WhiteNoise.ar(0.1)}.play

この後には、Subtractive Synthesisのソースとし使う色々なオシレータとノイズ源とフィルタが以下のように紹介されている。

前回やったように、キーボードショートカットでソースコードを引くのは「Cmd+j」である。 せっかくなので、ここで登場したものはソースを以下のように出してみた。 ピンクノイズとかHPFは中身が無くて、詐欺みたいなものだった(^_^;)。

Saw : UGen {
	*ar { arg freq=440.0, mul = 1.0, add = 0.0;
		^this.multiNew('audio', freq).madd(mul, add)
	}
	*kr { arg freq=440.0, mul = 1.0, add = 0.0;
		^this.multiNew('control', freq).madd(mul, add)
	}
}

Blip : UGen {
	*ar { arg freq=440.0, numharm = 200.0, mul = 1.0, add = 0.0;
		^this.multiNew('audio', freq, numharm).madd(mul, add)
	}
	*kr { arg freq=440.0, numharm = 200.0, mul = 1.0, add = 0.0;
		^this.multiNew('control', freq, numharm).madd(mul, add)
	}
}

PinkNoise : WhiteNoise {}

LFNoise0 : UGen {

	*ar { arg freq=500.0, mul = 1.0, add = 0.0;
		^this.multiNew('audio', freq).madd(mul, add)
	}
	*kr { arg freq=500.0, mul = 1.0, add = 0.0;
		^this.multiNew('control', freq).madd(mul, add)
	}
}

HPF : LPF {}

BPF : Filter {

	*ar { arg in = 0.0, freq = 440.0, rq = 1.0, mul = 1.0, add = 0.0;
		^this.multiNew('audio', in, freq, rq).madd(mul, add)
	}
	*kr { arg in = 0.0, freq = 440.0, rq = 1.0, mul = 1.0, add = 0.0;
		^this.multiNew('control', in, freq, rq).madd(mul, add)
	}
}

Resonz : Filter {

	*ar { arg in = 0.0, freq = 440.0, bwr = 1.0, mul = 1.0, add = 0.0;
		^this.multiNew('audio', in, freq, bwr).madd(mul, add)
	}
	*kr { arg in = 0.0, freq = 440.0, bwr = 1.0, mul = 1.0, add = 0.0;
		^this.multiNew('control', in, freq, bwr).madd(mul, add)
	}
}
具体例として、以下はLFノイズにレゾナンスフィルタをかけた例である。 いいカンジの、 こんな音がしてきた。

{Resonz.ar(LFNoise0.ar(400),1000,0.1)}.play

もう一つの具体例として、以下はさらにLine.krでカットオフを変化させた例である。 こんな音がしてきた。


{Resonz.ar(LFNoise0.ar(400),Line.kr(10000,1000,10),0.1)}.play

上の例は圧縮形で書かれているが、以下のソースコードとまったく同等である。


(
	{
		var source, line, filter; 
		source=LFNoise0.ar(400);
		line=Line.kr(10000,1000,10);
		filter=Resonz.ar(source,line,0.1);
	}.play;
)

ここで、「wavetables and oscillators」というトピックがある。 UGenはwavetableから読み出したデータ列を「波形」と解釈して、繰り返し読み出すことができる。 テキストにある「波形のプロット」から、以下のように数値を変えていくつか試してみた。

以下の例は、wavatableとして奇数次数のサイン合成をバッファに設定し、これを周期波形として読み出して、 そのピッチをマウスのX座標で変化させたものである。 こんな音がしてきた。


b = Buffer.alloc(s, 512, 1); //make a Buffer storage area

b.sine1(1.0/[1,2,3,4,5,6], true, false, true);  //fill the Buffer with wavetable data

b.plot; //stored shape (not in special SuperCollider Wavetable format, for clarity)

{OscN.ar(b,MouseX.kr(10,1000),0,0.1)}.play  //OscN; N means non-interpolating, let's us sidestep explaining the interpolation part for now

ここにあった「Osc」と「OscN」のソースは以下である。


Osc : UGen {
	*ar {
		arg bufnum, freq=440.0, phase=0.0, mul=1.0, add=0.0;
		^this.multiNew('audio', bufnum, freq, phase).madd(mul, add)
	}
	*kr {
		arg bufnum, freq=440.0, phase=0.0, mul=1.0, add=0.0;
		^this.multiNew('control', bufnum, freq, phase).madd(mul, add)
	}
}

OscN : UGen {
	*ar {
		arg bufnum, freq=440.0, phase=0.0, mul=1.0, add=0.0;
		^this.multiNew('audio', bufnum, freq, phase).madd(mul, add)
	}
	*kr {
		arg bufnum, freq=440.0, phase=0.0, mul=1.0, add=0.0;
		^this.multiNew('control', bufnum, freq, phase).madd(mul, add)
	}
}

さて、ここからが楽音合成のもう一つの王道、「加算合成(Additive Synthesis)」である。 その基礎はもちろん、以下のサイン波である。 defaultで440Hzらしいが、ボリュームが最大で音が割れてしまい、とてもサインに聞こえない(^_^;)。 こんな音である。


{SinOsc.ar}.play

以下の例では、割れないように強度を「0.1」とした上で、ピッチの違う(400Hzと600Hzの完全5度ならともかく、敢えて微妙な音程?)の2つの音がする。 こんな音である。


{SinOsc.ar(400,0,0.1) + SinOsc.ar(660,0,0.1)}.play

・・・しかしこれは、いい音である。 およそ下のソと上のミである。計算してみると、400:660=1.65であった。
純正な「下のソと上のミ」は400Hzに対して666.6666....Hzである。 こんな音である。 これは交互に聞き比べしないと判らない誤差で、どちらもいい音である。
ちなみに12等分平均律では、「下のソと上のミ」は400Hzに対して672.71654..Hzである。 こんな音である。 文句なく、もっとも汚く濁っているのは、平均律である。 こんな音程の音楽を幼い頃から被爆して育つ子供が気の毒である。

上の例は、簡略形として以下のようにも書ける。「[」と「]」の登場である。


{SinOsc.ar([400,660],0,0.1)}..play
ここまでモノラルの片チャンネルだったが、次に登場するのは、「pan2」である。 これはモノラルのサウンドを入力として、ステレオの音場に展開する。 定義は以下のようになっている。

Pan2.ar(input signal, pan position)

pan position goes from -1 (hard left) to 1 (hard right)
以下の例では、ホワイトノイズをマウスのX座標によってパンニングしている。 こんな音である。

{SinOsc.ar(400,0,0.1) + SinOsc.ar(660,0,0.1)}.play

SuperColliderでは、マルチチャンネルのサウンドには配列を使う。配列も「[」と「]」で括る。 以下の例では、3チャンネルの音としてSuperColliderが生成しているが、たまたまコンピュータが2チャンネルステレオとなっているので、前述の2音と同じように聞こえる。


{SinOsc.ar([400,660,870],0,0.1)}.play
「Pan2」がモノラル入力の信号をステレオ信号に振り分けたのに対して、ステレオ以上のマルチチャンネルのサウンドをモノラルに合成するのが「Mix」である。 以下の例では、2チャンネルの音がミックスされてモノラルになる。

{Mix(SinOsc.ar([400,660],0,0.1))}.play
以下の例では、2音をステレオとして出したものをモノラルにミックスした後でマウスのX座標によってパンニングしている。 こんな音である。

{Pan2.ar(Mix(SinOsc.ar([400,660],0,0.1)),MouseX.kr(-1,1))}.play

加算合成additive synthesisでは、ここまでの道具を使うと、簡単にパーシャル成分を規定して合成できる。 たとえば、スペクトルの成分として「500*[0.5,1,1.19,1.56,2,2.51,2.66,3.01,4.1]」を定義した場合、 以下はこれらを静的に合成したサウンドの例となる。 これは無理に各パーシャルが最大のままなので割れているが、 こんな音である。


{Mix(SinOsc.ar(500*[0.5,1,1.19,1.56,2,2.51,2.66,3.01,4.1],0,0.1))}.play

ここに、それぞれのパーシャルの音量バランスをさらに指定すると、以下のようになる。 よりベルっぽくなった。 こんな音である。


{Mix(SinOsc.ar(500*[0.5,1,1.19,1.56,2,2.51,2.66,3.01,4.1],0,0.1))}.play

SuperColliderでは、シンタックスとして Propellerのspin言語 のように、「(1..10)」というようなものを許すらしい。 以下の例では、1倍音から10倍音までを一気に合成している。


(
	var n;
	n=10;
	{
		Mix(
			SinOsc.ar(250*(1..n),0,1/n)
		)
	}.plot;
)

昼から始めたが、ここで学科会議の時間が近づいてきたので、今日はここまでである。 次は「2.2 Mulandadd.html」ということになる。

2011年3月19日(土)

今日は午後イチで、久しぶりに Processing日記 を進めたので、こちらはちょっとだけになりそうだ。 Workshop materials for Computer Music (G6002)2.2 Mulandadd.html からである。乗算と加算、ということだろう。 乗算と加算があれば、スカラーの線形変換が可能となる。 最初の例は以下のように、昨日もやったサインの表示である。 「mul:1」ということで、これは振幅が1倍ということになる。

{SinOsc.ar(mul:1)}.plot

ここで「mul:0.5」とすると、以下のように振幅が半分になった。


{SinOsc.ar(mul:0.5)}.plot

また、「mul:2.0」とすると、以下のように波形がクリップした。


{SinOsc.ar(mul:0.5)}.plot

また、「mul:-1.0」とすると、以下のように波形の符号が反転した。 これで、乗算の数値は全ての実数でOKと確認できた。


{SinOsc.ar(mul:0.5)}.plot

以下の例では、マウスのY座標に対応して1.0から0.1までの値で音量を制御する。 こんな音がしてきた。


{SinOsc.ar(mul:MouseY.kr(1.0,0.1))}.play

もう一つの演算は加算である。オフセット値、という使い方により、線形変換が完備することになる。 以下の例では、1/10の振幅の波形に0.6のオフセットをかけている。


{SinOsc.ar(mul:0.1, add:0.6)}.plot

以下の例では、マウスのX座標で0.1から1.0までの振幅乗算、そしてマウスのY座標に対応して0.9から-0.9までのオフセット値で音量を制御する。 割れた場合には音色変化となる、つまり「ファズ」である(^_^;)。 こんな音がしてきた。


{SinOsc.ar(mul:MouseX.kr(0.1,1.0), add:MouseY.kr(0.9,-0.9))}.play

乗算には「*」が、加算には「+」が使える。 以下の2ペアは、それぞれ同等である。


{0.1*SinOsc.ar}.play
{SinOsc.ar(mul:0.1)}.play

{0.1*SinOsc.ar+0.5}.play
{SinOsc.ar(mul:0.1,add:0.2)}.play
もっとも基本的な「SinOsc UGen」のシンタックスと推奨入力値は以下である。

SinOsc.ar(freq, phase, mul, add)

Expected input values might be:
	freq: 20 to 10000 (Hz)
	phase: 0.0 to 1.0 through the cycle
	mul: 0.0 to 1.0 from silence to full amplitude
	add: 0.0 no offset
位相とオフセットのdefaultはゼロなので、以下の2つの例は同等である。

{SinOsc.ar(MouseX.kr(440,880), 0.0, 0.1, 0.0)}.play

{SinOsc.ar(MouseX.kr(440,880), mul:0.1)}.play
以下の4つの例も全て同等なので混乱しないで・・・とのことである(^_^;)。 音量については「mul: -20.dbamp」ということでデシベル表記もOKである。

{SinOsc.ar(440,0.0,0.1)}.play

{SinOsc.ar(mul:0.1)}.play

{0.1*SinOsc.ar}.play

{SinOsc.ar(440, mul: -20.dbamp)}.play
「変調」はまた後で取り上げるが、ここでの乗算と加算を、以下の例のような場合に使える、と補記されていた。 こんな音がしてきた。

{SinOsc.ar(SinOsc.ar(3,mul:40,add:440),0,0.1)}.play

上の例は低速の周波数変調だったので「ビブラート」であるが、以下のようにスピードをぐんと上げれば立派にFMサウンドである。 こんな音がしてきた。


{SinOsc.ar(SinOsc.ar(100,mul:1000,add:2000),0,0.3)}.play

次の項目は、 Workshop materials for Computer Music (G6002)2.3 Controlling Synths.html である。


a = {SinOsc.ar(440)*0.1}.play
としておくと、「a」の定義をした上で発音する。 ここで、

a.run(false)
a.run(0)
のいずれかを実行すると、その「a」のサウンドだけ止まる。 改めて、メモリに残っている「a」を起動するのは、以下の3つのうちのいずれかでよい。

a.run(true)
a.run(1)
a.run
次のトピックは「Arguments」である。

a = {arg freq=440; SinOsc.ar(freq)*0.1}.play
を実行すると440Hzのサインが鳴るが、ここで引き続き

a.set(\freq,330)
を実行すると、330Hzに変更できる。これは定義の段階で「arg freq=440;」としていたからである。 以下の例は「これをやってもCRASHするよ(^_^;)」という、駄目な例である。UGenをアーギュメントにしては駄目である。

a = {arg freq=440; SinOsc.ar(freq)*0.1}.play

a.set(\freq,MouseX.kr(220,440))

ERROR: can't set a control to a UGen
CALL STACK:
	Exception:reportError   174FC500
		arg this = 
	Nil:handleError   17500EE0
		arg this = nil
		arg error = 
	Thread:handleError   175350B0
		arg this = 
		arg error = 
	Object:throw   174FB1E0
		arg this = 
	UGen:asControlInput   17545570
		arg this = 
	Object:asOSCArgEmbeddedArray   17536B50
		arg this = 
		arg array = [*1]
	< FunctionDef in Method Array:asOSCArgArray >   174F9FA0
		arg e = 
	ArrayedCollection:do   1A4667D0
		arg this = [*2]
		arg function = 
		var i = 1
	Array:asOSCArgArray   175333B0
		arg this = [*2]
		var array = [*1]
	Node:set   1A466B30
		arg this = 
		arg args = [*2]
	Interpreter:interpretPrintCmdLine   174B6520
		arg this = 
		var res = nil
		var func = 
		var code = "a.set(\freq,MouseX.kr(220,440))"
		var doc = 
	Process:interpretPrintCmdLine   174F9760
		arg this = 
アーギュメントは2つ同時に変更することも出来る。 以下の例では、最初に起動したサウンドから、次を何度か実行すると こんな音がしてきた。

a = {arg freq=440, amp=0.1; SinOsc.ar(freq)*amp}.play

a.set(\freq,rrand(220,440), \amp, rrand(0.05,0.2))

これで「2. Sound Synthesis 1」が終わりである。 次は「3. Sound Synthesis 2」からである。変調の面白いサウンドを期待しよう。

2011年3月20日(日)

今日も終日の余裕があるが、卒展の学生作品記録の整理などしていると午後になった。 昨日は Processing日記 も進めたので、今日はSuperColliderを攻めてみよう。 Workshop materials for Computer Music (G6002)3. Sound Synthesis 23.1 Envelopes.html からである。 最初のトピックは「Time varying sounds」であり、変調にはこの概念が欠かせない。

これまでの音は以下のように鳴りっぱなしでキー入力により音を止めた。


{SinOsc.ar(440,0,0.1)}.play

これを以下のように、直線的な時間的変化を加えることによってサウンドが自己完結し、音楽の一つの要素となる。 こんな音である。


{SinOsc.ar(440,0,Line.kr(0.1,0.0,1.0))}.play

一般的に、サウンドのパラメータが「発音」をスタートとして時間的変化する場合、この変化特性(カーブ)を「エンベロープ」と言う。音量とかピッチのエンベロープが有名だが、あらゆるパラメータが時間的変化(タイムバリアント)し得る。 このエンベロープを生成する「Env()」という関数は非常に巨大で強力である。 ヘルプを引くと、その中身は以下であった。


Env {
	// envelope specification for an EnvGen, Env is not a UGen itself
	var  0,
			\lin -> 1,
			\linear -> 1,
			\exp -> 2,
			\exponential -> 2,
			\sin -> 3,
			\sine -> 3,
			\wel -> 4,
			\welch -> 4,
			\sqr -> 6,
			\squared -> 6,
			\cub -> 7,
			\cubed -> 7
		];
	}

	levels_ { arg z;
		levels = z;
		array = nil;
	}
	times_ { arg z;
		times = z;
		array = nil;
	}
	curves_ { arg z;
		curves = z;
		array = nil;
	}
	releaseNode_ { arg z;
		releaseNode = z;
		array = nil;
	}
	loopNode_ { arg z;
		loopNode = z;
		array = nil;
	}
	== { arg that;
		^this.compareObject(that,['levels','times','curves','releaseNode','loopNode'])
	}

	asArray {
		if (array.isNil) { array = this.prAsArray }
		^array
	}

	at { arg time;
		var array = this.asArray;
		^if(time.isSequenceableCollection) {
			time.collect { |t| array.envAt(t) }
		} {
			array.envAt(time)
		}
	}

	asPseg {
		var c = if(curves.isSequenceableCollection.not) { curves } { Pseq(curves) };
		^Pseg(Pseq(levels), Pseq(times ++ [1.0]), c) // last time is a dummy
	}

	// methods to make some typical shapes :

	// fixed duration envelopes
	*triangle { arg dur=1.0, level=1.0;
		dur = dur * 0.5;
		^this.new(
			[0, level, 0],
			[dur, dur]
		)
	}
	*sine { arg dur=1.0, level=1.0;
		dur = dur * 0.5;
		^this.new(
			[0, level, 0],
			[dur, dur],
			'sine'
		)
	}
	*perc { arg attackTime=0.01, releaseTime=1.0, level=1.0, curve = -4.0;
		^this.new(
			[0, level, 0],
			[attackTime, releaseTime],
			curve
		)
	}
	*linen { arg attackTime=0.01, sustainTime=1.0, releaseTime=1.0, level=1.0, curve = \lin;
		^this.new(
			[0, level, level, 0],
			[attackTime, sustainTime, releaseTime],
			curve
		)
	}

	// envelopes with sustain
	*cutoff { arg releaseTime = 0.1, level = 1.0, curve = \lin;
		var curveNo = this.shapeNumber(curve);
		var releaseLevel = if(curveNo==2){
			-100.dbamp
		}{
			0
		};
		^this.new([level, releaseLevel], [releaseTime], curve, 0)
	}
	*dadsr { arg delayTime=0.1, attackTime=0.01, decayTime=0.3,
			sustainLevel=0.5, releaseTime=1.0,
				peakLevel=1.0, curve = -4.0, bias = 0.0;
		^this.new(
			[0, 0, peakLevel, peakLevel * sustainLevel, 0] + bias,
			[delayTime, attackTime, decayTime, releaseTime],
			curve,
			3
		)
	}
	*adsr { arg attackTime=0.01, decayTime=0.3,
			sustainLevel=0.5, releaseTime=1.0,
				peakLevel=1.0, curve = -4.0, bias = 0.0;
		^this.new(
			[0, peakLevel, peakLevel * sustainLevel, 0] + bias,
			[attackTime, decayTime, releaseTime],
			curve,
			2
		)
	}

	*asr { arg attackTime=0.01, sustainLevel=1.0, releaseTime=1.0, curve = -4.0;
		^this.new(
			[0, sustainLevel, 0],
			[attackTime, releaseTime],
			curve,
			1
		)
	}

	releaseTime {
		if(releaseNode.notNil,{
			^times.copyRange(releaseNode,times.size - 1).sum
		},{
			^0.0 // ?
		})
	}

	// blend two envelopes
	blend { arg argAnotherEnv, argBlendFrac=0.5;
		^this.class.new(
			levels.blend(argAnotherEnv.levels, argBlendFrac),
			times.blend(argAnotherEnv.times, argBlendFrac),
			curves.blend(argAnotherEnv.curves, argBlendFrac),
			releaseNode,
			loopNode
		)
	}

	// delay the onset of the envelope
	delay { arg delay;
		^Env([levels[0]] ++ levels,
			[delay] ++ times,
			if (curves.isArray) {[\lin] ++ curves} {curves},
			if(releaseNode.notNil) {releaseNode = releaseNode + 1},
			if(loopNode.notNil) {loopNode = loopNode + 1}
		)
	}

	// connect releaseNode (or end) to first node of envelope
	circle { arg timeFromLastToFirst = 0.0, curve = 'lin';
		var first0Then1 = Latch.kr(1.0, Impulse.kr(0.0));
		if(releaseNode.isNil) {
			levels = [0.0]++ levels ++ 0.0;
			curves = [curve]++ curves.asArray.wrapExtend(times.size) ++ 'lin';
			times  = [first0Then1 * timeFromLastToFirst] ++ times ++ inf;
			releaseNode = levels.size - 2;
		} {
			levels = [0.0]++ levels;
			curves = [curve]++ curves.asArray.wrapExtend(times.size);
			times  = [first0Then1 * timeFromLastToFirst] ++ times;
			releaseNode = releaseNode + 1;
		};
		loopNode = 0;
	}
	
	/*
	plot {
		var timeScale;
		timeScale = 0.01 / times.sum;
		Synth.plot({ arg synth;
			synth.releaseTime = 0.005;
			EnvGen.ar(this, 1, 0, 1, 0, timeScale)
		}, 0.01)
	}
	*/

	isSustained {
		^releaseNode.notNil
	}

	shapeNumber { arg shapeName;
		this.deprecated(thisMethod, this.class.class.findMethod(\shapeNumber));
		^this.class.shapeNumber(shapeName)
	}
	*shapeNumber { arg shapeName;
		var shape;
		if (shapeName.isValidUGenInput) { ^5 };
		shape = shapeNames.at(shapeName);
		if (shape.notNil) { ^shape };
		Error("Env shape not defined.").throw;
	}
	curveValue { arg curve;
		if (curve.isValidUGenInput, { ^curve },{ ^0 });
	}

//	send { arg netAddr, bufnum;
//		var array;
//		array = this.asArray;
//		netAddr.sendMsg("buf.setn", bufnum, 0, array.size, *array);
//	}

	test { arg releaseTime = 3.0;
		var id, def, s;
		s = Server.default;
		if(s.serverRunning.not) { "Server not running.".warn; ^this };
		id = s.nextNodeID;
		fork {
			def = { arg gate=1;
				Out.ar(0,
					SinOsc.ar(800, pi/2, 0.3) * EnvGen.ar(this, gate, doneAction:2)
				)
			}.asSynthDef;
			def.send(s);
			s.sync;
			s.sendBundle(s.latency, [9, def.name, id]);
			if(s.notified) {
				OSCpathResponder(s.addr, ['/n_end', id], { |time, responder, message|
					s.sendMsg(\d_free, def.name);
					responder.remove;
				}).add;
			};
			if(this.isSustained) {
				s.sendBundle(s.latency + releaseTime, [15, id, \gate, 0])
			};
		};
	}

	storeArgs { ^[levels, times, curves, releaseNode, loopNode] }

	prAsArray {
		var contents, curvesArray;
		contents = [levels.at(0), times.size,
				releaseNode ? -99, loopNode ? -99];
		curvesArray = curves.asArray;
		times.size.do({ arg i;
			contents = contents ++ [
				levels.at(i+1),
				times.at(i),
				this.class.shapeNumber(curvesArray.wrapAt(i)),
				this.curveValue(curvesArray.wrapAt(i))
			];
		});
		^contents
	}

	discretize {arg n = 1024;
		^this.asSignal(n);
	}

	range { |lo=0, hi=1|
		^this.class.new(levels.linlin(levels.minItem, levels.maxItem, lo, hi),
			times, curves, releaseNode, loopNode)
	}

	exprange { |lo=0, hi=1|
		^this.class.new(levels.linexp(levels.minItem, levels.maxItem, lo, hi),
			times, curves, releaseNode, loopNode)
	}

}
また、エンベロープを生成するUGenの「EnvGen()」という関数のヘルプを引くと、その中身は以下であった。

EnvGen : UGen { // envelope generator
	*ar { arg envelope, gate = 1.0, levelScale = 1.0, levelBias = 0.0, timeScale = 1.0, doneAction = 0;
		^this.multiNewList(['audio', gate, levelScale, levelBias, timeScale, doneAction, `envelope])
	}
	*kr { arg envelope, gate = 1.0, levelScale = 1.0, levelBias = 0.0, timeScale = 1.0, doneAction = 0;
		^this.multiNewList(['control', gate, levelScale, levelBias, timeScale, doneAction, `envelope])
	}
	*new1 { arg rate, gate, levelScale, levelBias, timeScale, doneAction, envelope;

		^super.new.rate_(rate).addToSynth.init([gate, levelScale, levelBias, timeScale, doneAction]
			++ envelope.dereference.asArray);
	}
 	init { arg theInputs;
 		// store the inputs as an array
 		inputs = theInputs;
 	}
	argNamesInputsOffset { ^2 }
}
一例として、以下の場合には、最初の配列で3点のY座標が与えられているので、初期値は1.0でここから0.0に向かい、最後はまた1.0に至る。2つ目の配列がX方向の値であり、ここでは1.0と0.5とに、つまり2:1に分割されている。X方向の単位は秒(sec)である。

Env([1,0,1],[1.0,0.5]).plot

2つ目の配列の中身を以下のように6と3としても、2:1に分割されているが、この場合には6秒かけて下がり、3秒かけて上昇する。


Env([1,0,1],[6,3]).plot

エンベロープには以下のように色々な形状が定義されている。詳しくは、 コンピュータサウンドの世界 を参照されたい。


//various types of envelope:

Env([0,1,0],[1.0,0.5]).plot  //one second 0 to 1 then half a second 1 to 0

Env.linen(0.03,0.5,0.1).plot  //linen has arguments attack, sustain, release, level, curve

Env.adsr(0.01, 0.5, 0.5, 0.1).plot  //attack, decay, sustain level, release, level, curve

Env.perc(0.05,0.5,1.0,0).plot //arguments attack, release, level, curve - good for percussive hit envelopes

エンベローブのテーブル「Env()」をエンベロープ生成の「EnvGen()」に食わせれば、時間変化のパラメータが実装できる。 以下の例では こんな音がする。


{SinOsc.ar(440,0,0.1)*EnvGen.kr(Env([1,0],[1.0]))}.play

ここからいよいよ、実際に使えるサウンドのためのエンベロープを目指そう。 まず最初に、以下のように1000から20まで、1秒間かけて下降するエンベロープを用意する。つまりY軸方向はいくつでも対応可能である。


Env([1000,20],[1.0]).plot

これを「EnvGen()」に食わせたものを、以下のように鋸歯状波UGenの「Saw」の周波数として与えると、 こんな音がする。


{SinOsc.ar(440,0,0.1)*EnvGen.kr(Env([1,0],[1.0]))}.play

もう少し複雑にするために、上記の例を

とすれば、

{Saw.ar(EnvGen.kr(Env([10000,20],[0.5])),EnvGen.kr(Env([0.1,0],[2.0])))}.play
となるが、これではカッコのネスティングでわけが判らない(^_^;)。 そこで、まったく同等だが、以下のように記述すると理解しやすい。 こんな音がした。

(
	{
		Saw.ar(
			EnvGen.kr(Env([10000,20],[0.5])),  //frequency input
			EnvGen.kr(Env([0.1,0],[2.0]))          //amplitude input
		)
	}.play
)

ここで、「EnvGen.kr(Env()」と「EnvGen.ar(Env()」の違いについてコメントがあった。 我々の耳にはどちらも同じに聞こえるが、エンベロープの変化はオーディオレートで行うのは無駄に細かいので、CPUの負担を軽減するためにも「kr」を推奨する、ということであった。

エンベロープのもう一つの大きな意義として、ライブで生起する事象の最後に、もう終了したのでメモリを解放する、と記述できるオプションがある。 以下の例では、「doneAction」というアーギュメントにより、エンベロープが最後まで行くと、そこでメモリを解放してCPUの負担を軽減する。 こんな音がした。


//FM sound

(
	{
		SinOsc.ar(
			SinOsc.ar(10,0,10,440),
			0.0,
			EnvGen.kr(Env([0.5,0.0],[1.0]), doneAction:2)

		)
	}.play
)

すでに登場している「Line」は直線的なエンベロープを生成するが、「XLine」は指数関数的なエンベロープを生成する。 人間の聴覚は指数関数なので、これは重要である。 以下の例は直線的なエンベロープである。 こんな音がした。


{Saw.ar(SinOsc.ar(1,0,10,440),Line.kr(0,1,1,doneAction:2))}.play

以下の例は指数関数的エンベロープである。指数特性なので初期値も変更していることに注意。 こんな音がした。


{Saw.ar(SinOsc.ar(1,0,10,440),XLine.kr(0.0001,1,1,doneAction:2))}.play

前述のパーカッシブな減衰は、ADSRで言えばDecayでゼロに漸近する。 これは、まさに打楽器のように、発音の開始だけのトリガであとは音が勝手に減衰して消えていく。 これに対して、残響のある場所でのオルガンとかピアノなど、次はReleaseの処理である。 まず、以下の例では、ゼロから上昇して、次いでゼロに減衰する。 こんな音がする。


{EnvGen.ar(Env([0,0.1,0],[0.1,0.9]),doneAction:2)*SinOsc.ar(330)}.play

しかしここで欲しいのは、発音開始から上昇した音はそのまま無限に続いて、後から「鍵盤を離す」という(MIDI Note Off)リリースが来たところで減衰のフェーズに進みたい。 そこで、以下の例では「Env.asr」に「release」というパラメータを送ってこれを実現している。 こんな音がする。


a = {EnvGen.ar(Env.asr(0.1,0.1,1.0),doneAction:2)*SinOsc.ar(330)}.play //sound continues

a.release(2.0); //let it finish, taking 2.0 seconds to fade out

また、同じことを実現するのに、以下の例では「gate」という変数を定義して、まず「a」としてgate=1の発音を開始し、後で「a.set」によって変数をgate=0として書き換える。これにより、その時点からリリースのフェーズに進む。 どちらも同じであるが、発音持続情報(Key ON から Key Off)をgateという変数として他にも使えるメリットがある。 こんな音がする。


a = {arg gate=1; EnvGen.ar(Env.asr(0.1,0.1,0.9),gate,doneAction:2)*SinOsc.ar(330)}.play

a.set(\gate,0) //when gate is set to 0, the envelope can finish, and takes 0.9 seconds to fade

リリースのノード数は、以下の例のようにいくつも規定できる。 こんな音がする。


e = Env([0.0,1.0,0.0],[0.1,3.0],0,1); //releaseNode at node 1, which is the pair of 0.0 level in the first array and 3.0 seconds in the second. 

a= {arg gate=1; EnvGen.ar(e,gate,doneAction:2)*SinOsc.ar(550,0,0.1)}.play

a.set(\gate, 0); //takes 3.0 seconds to fade out

最後は「Looping envelopes」である。 以下のように、上記の多数のリリースノードを定義した上で、「リリースノード数-1」と「ループノード(リリースノード数より小さいこと)」との間でルーブする。 こんな音がする。


e = Env([0.0,0.0,1.0,0.0],[0.5,1.0,2.0],0,2,0); //releaseNode at 2, loopNode at 0

a= {arg gate=1; EnvGen.ar(e,gate,doneAction:2)*SinOsc.ar(550,0,0.1)}.play

a.set(\gate, 0); //takes 2.0 seconds to fade out

以下の例では、ループエンベロープを高速にすることで、音色的な効果を得ている。 マウスのX座標でピッチをぐりぐりできる。 こんな音がした。


e = Env([0.0,1.0,-1.0,0.0],[0.01,0.01,2.0],0,2,0); //releaseNode at 2, loopNode at 0

e.plot

a= {arg gate=1; EnvGen.ar(e,gate,timeScale:MouseX.kr(0.1,2.0),doneAction:2)}.play

a.set(\gate, 0); //stops also immediately since release transition to 0.0 is too slow to be a pitched oscillation

これでエンベロープはおしまいである。 ようやく、この道具を使って、次に「変調」に進むことになる。

2011年3月23日(水)

連休3日目の21日は学生自主制作作品の実験に付き合ったりしてナシ、翌22日は学科会議・教授会・送別会などがありナシ、とお休みが続いた。今日もいろいろ雑事があり午後だけだが、大阪に行くまであと3日しかないので、 Processing は棚上げして、行けるところまでSuperColliderを進めることにしよう。 Workshop materials for Computer Music (G6002)3. Sound Synthesis 23.2 Modulation synthesis.html からである。 いよいよ「変調合成(modulation synthesis)」である。

この章では、internal serverを起動した上で、スペクトルアナライザとして「Lance Putnam's Frequency Scope」をバックグラウンドで起動しておく、とのことである。 しかし何故かinternal serverをーは起動しないので、localhost serverを起動して「FreqScope.new」を実行してみた。 以下のようになった。

この「FreqScope」というのをヘルプで引くと、以下のようなものらしい。まだ詳細は不明である。(^_^;)


FreqScope : ViewRedirect { // redirects to SCFreqScopeWindow
	*new { arg width=512, height=300, busNum=0, scopeColor, bgColor;
		busNum = busNum.asControlInput;
		^this.implClass.new(width, height, busNum, scopeColor)
		}
	*key { ^\freqScope }
}
しかしこれだけで何も起きないので諦めて(^_^;)、中身に進んだ。 「modulation synthesis」では2種類の信号があり、一つはcarrierである。これをもう一つのmodulatorで変調する。
AM放送の電波は、それぞれの周波数(1440kHzなど)のキャリヤの振幅を、音声信号をモジュレータとして変調して、これを電磁波として空間に放出している。 AMラジオではこの電磁波を、聞きたい放送の周波数だけを通して(同調回路)、そのキャリヤを検波(整流)して平滑(積分)すると、キャリヤの振幅の時間変化、つまり元の変調信号そのものが電圧として得られ、これをアンプで増幅してスピーカからサウンドとして聞いている。
FM放送の電波は、それぞれの周波数(82.1MHzなど)のキャリヤの周波数を、音声信号をモジュレータとして変調して、これを電磁波として空間に放出している。 FMラジオではこの電磁波を、聞きたい放送の周波数だけを通して(同調回路)、その周波数の変動をf-vコンバータによってキャリヤ周波数の時間変化、つまり元の変調信号そのものとして得て、これをアンプで増幅してスピーカからサウンドとして聞いている。
SuperColliderでは、このような変調がいろいろに実現できるので、スペクトル的に面白いサウンドがたくさん作れる。

最初のサンプルは「リング変調」である。これは「carrier * modulator」ということで、2つの信号を単純に乗算するだけである。 以下の例では、マウスカーソルのX座標とY座標のそれぞれに対応させて指数特性で変化する周波数のサインを生成し、その片方をcarrier、もう一方をmodelaturとして単純に乗算させている。 リング変調は振幅変調(AM)と違って、プラスマイナスの幅で両方とも乗算してゼロクロスを越える。 こんな音がした。


(
	{
		var carrier, modulator, carrfreq, modfreq;
		carrfreq= MouseX.kr(440,5000,'exponential');
		modfreq= MouseY.kr(1,5000,'exponential');
		carrier= SinOsc.ar(carrfreq,0,0.5);
		modulator= SinOsc.ar(modfreq,0,0.5);
		carrier*modulator;
	}.play
)

単純なサイン波では、キャリヤの周波数をC、モジュレータの周波数をMとすると、これを乗算したリング変調の出力信号は、「C+M」と「C-M」の2つのスペクトルを持つ。 以下の三角関数の「積和の公式」から明らかである。


cos(C)*cos(M) = 0.5*(cos(C-M) + cos(C+M))
次のサンプルは「振幅変調 Amplitude Modulation (AM)」である。これも「carrier * modulator」ということで、2つの信号を単純に乗算するだけである。ただしリング変調と違って、モジュレータが非負、つまりユニポーラとなっている。 ユニポーラとバイポーラは以下のように指定できる。

{SinOsc.ar(440,0,0.5)}.scope //bipolar, -0.5 to 0.5

{SinOsc.ar(440,0,0.5,0.5)}.scope //unipolar, 0 to 1 (0.5 plus or minus 0.5)
以下の例では、マウスカーソルのX座標とY座標のそれぞれに対応させて指数特性で変化する周波数のサインを生成し、AM変調させている。 こんな音がした。

(
	{
		var carrier, modulator, carrfreq, modfreq;
		carrfreq= MouseX.kr(440,5000,'exponential');
		modfreq= MouseY.kr(1,5000,'exponential');
		carrier= SinOsc.ar(carrfreq,0,0.5);
		modulator= SinOsc.ar(modfreq,0,0.25, 0.25);
		carrier*modulator;
	}.play
)

リング変調のサウンドと振幅変調のサウンドの違いは、モジュレータをユニポーラ化するために1を足しているために、以下のように「C+M」と「C-M」にさらに「C」のスペクトルも加わるからである。


cos(C)*(1+cos(M)) = cos(C)+ 0.5*(cos(C-M) + cos(C+M))
そして教科書通りに、次のサンプルは「周波数変調 Frequency Modulation (FM)」である。 サウンド生成のFM音源方式は、1967年にJohn Chowning博士が発明し1973年に刊行、そして1983年にYAMAHAが特許を買ってシンセサイザ「DX7」を発売し、これは世界中で30万台も売れた。

ここまでに既に出て来た「ビブラート」というのも、実はFMである。ただしモジュレータの周波数が非常に低かった。 FM音源ではモジュレータの周波数もキャリヤと同等に高くした、というのが画期的なアイデアである。 実は上述のRMでもAMでも「モジュレーションの深さ」というパラメータが存在するが、FMではその効果が大きいので、一般にFM方式には以下の3つのパラメータがある。

2つのパラメータであればマウスのXとYとで実験できるが、3つとなると無理なので、以下の例では、新しいウインドウに3つのスライダーを表示させて、これで上の3つのパラメータをそれぞれ変更できるようになっていて、新しいGUIのサンプルでもある。 あれこれいじると、 こんな音がした。

(
	var w, carrfreqslider, modfreqslider, moddepthslider, synth;
	w=Window("frequency modulation", Rect(100, 400, 400, 300));
	w.view.decorator = FlowLayout(w.view.bounds);

	synth= {
		arg carrfreq=440, modfreq=1, moddepth=0.01; 
		SinOsc.ar(carrfreq + (moddepth*SinOsc.ar(modfreq)),0,0.25)
	}.play;

	carrfreqslider= EZSlider(w, 300@50, "carrfreq", ControlSpec(20, 5000, 'exponential', 10, 440), 
		{|ez|  synth.set(\carrfreq, ez.value)});
	w.view.decorator.nextLine;
	modfreqslider= EZSlider(w, 300@50, "modfreq", ControlSpec(1, 5000, 'exponential', 1, 1), 
		{|ez|  synth.set(\modfreq, ez.value)});
	w.view.decorator.nextLine;
	moddepthslider= EZSlider(w, 300@50, "moddepth", ControlSpec(0.01, 5000, 'exponential', 0.01, 0.01),
		 {|ez|  synth.set(\moddepth, ez.value)});
	w.front;
)

FMが登場して劇的にウケたのは、その多彩・豊富なスペクトル成分にある。 デプスを軽くしても、以下のような成分が出て来るので、デプスを深くすれば、さらに凄いことになる。


C + kM where k is any integer, ie. C. C+M, C-M, C+2M, C-2M, ...
ヤマハのFM音源では、音楽的により有効になるように、パラメータとしてデプスでなく、インデックス「I= D/M」を使っている。 以下の例では、デプスでなくインデックスを変更してサウンドを試せる。 こんな音がした。

(
	var w, carrfreqslider, modfreqslider, modindexslider, synth;
	w=Window("frequency modulation via modulation index", Rect(100, 400, 400, 300));
	w.view.decorator = FlowLayout(w.view.bounds);

	synth= {
		arg carrfreq=440, modfreq=1, modindex=0; 
		SinOsc.ar(carrfreq + (modindex*modfreq*SinOsc.ar(modfreq)),0,0.25)
	}.play;

	carrfreqslider= EZSlider(w, 300@50, "carrfreq", ControlSpec(20, 5000, 'exponential', 10, 440), 
		{|ez|  synth.set(\carrfreq, ez.value)});
	w.view.decorator.nextLine;
	modfreqslider= EZSlider(w, 300@50, "modfreq", ControlSpec(1, 5000, 'exponential', 1, 1), 
		{|ez|  synth.set(\modfreq, ez.value)});
	w.view.decorator.nextLine;
	modindexslider= EZSlider(w, 300@50, "modindex", ControlSpec(0.0, 10, 'linear', 0.01, 0.0), 
		{|ez|  synth.set(\modindex, ez.value)});
	w.front;
)

以下の例では、ふたたびマウスのXとYという2つで、「modf」と「ind」という2つの変数を動かしている。 ぐりぐりすると、 こんな音がした。


(
	{
		var modf, ind;
		modf= MouseX.kr(1,440, 'exponential');
		ind=MouseY.kr(0.0,10.0);
		SinOsc.ar(SinOsc.ar(modf,0,modf*ind, 440),0,0.25)
	}.play
)

Moore博士の名著「Elements of Computer Music」、あるいはMax/MSPのチュートリアル「Tutorial 11; Frequency Modulation」には、FM音源で重要な「harmonicity ratio」について解説してある。 整数kに対して、サイドバンドのエネルギーは「Fc+(k*Fm)」で分配されるので、もし「Fm = h*Fc」という関係にあれば、全てのサイドバンドのエネルギーはFcと整数比の関係になる。 以下の例では、マウスのXで「harmonicity」と「modindex」を、マウスのYで「fc」を動かしている。 ぐりぐりすると、 こんな音がした。


(
	{
		var fc, harmonicity, fm, modindex; 
			//fc is frequency of carrier
			//fm is frequency of modulator
		fc= 440; //MouseY.kr(330,550); 
		harmonicity= MouseX.kr(0,10).round(1); 
		modindex= MouseY.kr(0.0,10.0);
			//which is really modulation amplitude/modulation frequency, 
			//acts as brightness control as energy distribution changed over components
		fm= fc*harmonicity; //since harmonicity is an integer, 
		SinOsc.ar(fc+(SinOsc.ar(fm)*fm*modindex), 0.0,0.1); 
	}.play
)

変調の最後の例は「位相変調(Phase Modulation)」である。 これはあまり馴染みが無いが、SinOscには


SinOsc.ar(freq, phase, mul, add)
という4つのパラメータがあるので、このphaseの項を変調してやれば、何か出て来るだろう、という事である。 まぁ、激しく変化させればほとんどFMと違わないのでは・・・と予想できる。 以下の例では、3つのスライダーで「carrfreqslider」「modfreqslider」「modindexslider」の3つを動かしている。 ぐりぐりすると、案の定、 こんな音がした。

(
	var w, carrfreqslider, modfreqslider, modindexslider, synth;
	var conversion= 2pi/(s.sampleRate); //needed to avoid phase being adjusted too wildly
	w=Window("phase modulation via modulation index", Rect(100, 400, 400, 300));
	w.view.decorator = FlowLayout(w.view.bounds);

	synth= {
		arg carrfreq=440, modfreq=1, modindex=0; 
		SinOsc.ar(carrfreq, ( (modfreq*modindex)*conversion*SinOsc.ar(modfreq)),0.25)
	}.play;

	carrfreqslider= EZSlider(w, 300@50, "carrfreq", ControlSpec(20, 5000, 'exponential', 10, 440), 
		{|ez|  synth.set(\carrfreq, ez.value)});
	w.view.decorator.nextLine;
	modfreqslider= EZSlider(w, 300@50, "modfreq", ControlSpec(1, 5000, 'exponential', 1, 1), 
		{|ez|  synth.set(\modfreq, ez.value)});
	w.view.decorator.nextLine;
		//bigger range since adjusting phase directly and not frequency
	modindexslider= EZSlider(w, 300@50, "modindex", ControlSpec(0.0, 100, 'linear', 0.01, 0.0), 
		{|ez|  synth.set(\modindex, ez.value)});
	w.front;
)

以下の例はマウスで制御した位相変調の音源である。 位相の変化が周波数なので、やはりFMの音である。 こんな音がした。


(
	{
		var modf, ind, conversion;
		modf= MouseX.kr(1,1000, 'exponential');
		ind=MouseY.kr(0.0,100.0);
		conversion= 2pi/(s.sampleRate); 
			//Phasor is a UGen which will loop around a given interval, 
			//in this case 0 to 2pi, taking us around the waveform 
			//of the sinusoid; note that all the action is in the phase input
		SinOsc.ar(0, Phasor.ar(0,440*conversion,0,2pi)+( (modf*ind)*conversion*SinOsc.ar(modf)), 0.25)
	}.play
)

位相変調オシレータ「PMOsc」は以下のように定義されている。


PMOsc  {
	*ar { arg carfreq,modfreq,pmindex=0.0,modphase=0.0,mul=1.0,add=0.0;
		^SinOsc.ar(carfreq, SinOsc.ar(modfreq, modphase, pmindex),mul,add)
	}
	*kr { arg carfreq,modfreq,pmindex=0.0,modphase=0.0,mul=1.0,add=0.0;
		^SinOsc.kr(carfreq, SinOsc.kr(modfreq, modphase, pmindex),mul,add)
	}
}
他に参照するものとして、以下は「SinOscFB」のヘルプである。

SinOscFB : UGen {
	*ar {
		arg freq=440.0, feedback=0.0, mul=1.0, add=0.0;
		^this.multiNew('audio', freq, feedback).madd(mul, add)
	}
	*kr {
		arg freq=440.0, feedback=0.0, mul=1.0, add=0.0;
		^this.multiNew('control', freq, feedback).madd(mul, add)
	}
}
他に参照するものとして、以下は「Vibrato」のヘルプである。

Vibrato : UGen {
	*ar {
		arg freq = 440.0, rate = 6, depth = 0.02, delay = 0.0, onset = 0.0,
				rateVariation = 0.04, depthVariation = 0.1, iphase = 0.0;
		^this.multiNew('audio', freq, rate, depth, delay, onset, rateVariation, depthVariation, iphase)
	}
	*kr {
		arg freq = 440.0, rate = 6, depth = 0.02, delay = 0.0, onset = 0.0,
				rateVariation = 0.04, depthVariation = 0.1, iphase = 0.0;
		^this.multiNew('control', freq, rate, depth, delay, onset, rateVariation, depthVariation, iphase)
	}
}
これで「変調」はおしまいである。 だいぶいい音がしてきた。 次は Workshop materials for Computer Music (G6002)3. Sound Synthesis 23.3 Moresynthesisexamples.html である。

この章の最初のトピックは「Fat Chorus」である。 アナログシンセには必須のサウンドである。 もっとも簡単なコーラスは以下のように、ピッチをわずかにデチューンさせた鋸歯状波オシレータをミックスさせることである。 これは、個々のパーシャルをサインと考えると、「和積」の公式から、それぞれに振幅変調がかかる効果に相当する。 こんな音がする。


{Mix(Saw.ar([440,443,437],0.1))}.play

より複雑にするには、AM、FM、コーラス、エンベロープなどを以下のように組み合わせる。 こんな音がした。


(
	{
		Mix(
			Resonz.ar( 
					//The Resonz filter has arguments input, freq, rq=bandwidth/centre frequency  
				Saw.ar([440,443,437] + SinOsc.ar(100,0,5*200)), 
					//frequency modulated sawtooth wave with chorusing
				XLine.kr(10000,10, 10, doneAction:2), 
					//vary filter bandwidth over time
				Line.kr(1,0.05, 10), 
					//vary filter rq over time
				mul: LFSaw.kr(Line.kr(13,17,3),0.5,0.5)*Line.kr(1,0,10)  
					//AM
			)
		)
	}.play
)

次のトピックは「Sample playback rate modulation」である。 サウンドファイルの扱いはまた別にあるが、ここではとりあえず「PlayBuf」と「Buffer」だけを使う。 「PlayBuf」のヘルプは以下である。


PlayBuf : MultiOutUGen {
	*ar { arg numChannels, bufnum=0, rate=1.0, trigger=1.0, startPos=0.0, loop = 0.0, doneAction=0;
		^this.multiNew('audio', numChannels, bufnum, rate, trigger, startPos, loop, doneAction)
	}

	*kr { arg numChannels, bufnum=0, rate=1.0, trigger=1.0, startPos=0.0, loop = 0.0, doneAction=0;
		^this.multiNew('control', numChannels, bufnum, rate, trigger, startPos, loop, doneAction)
	}

	init { arg argNumChannels ... theInputs;
		inputs = theInputs;
		^this.initOutputs(argNumChannels, rate);
	}
	argNamesInputsOffset { ^2 }
}
「Buffer」のヘルプはとんでもなく長いので別ファイルにした。 これである。

サウンドファイルは これ(voice.aiff)であるが、これをSuperColliderのフォルダの下の「sounds」というフォルダの中に入れておく。 そしてまず最初に、以下を実行する。


//run me first to load the soundfiles
(
	b=Buffer.read(s,"sounds/voice.aiff");
)
そして、以下の例ではマウスのXとYで、このサンプリングデータを読み出すレートと変調を制御している。 こんな音がした。

//now me!
(
	{
		var modf, ind, modulator;
		var b1; 
		b1= b.bufnum;
		modf= MouseX.kr(1,4400, 'exponential');
		ind=MouseY.kr(0.0,1.0, 'linear');
		modulator= SinOsc.kr(modf,0,10*modf*ind, 440); 
		PlayBuf.ar(1,b1, BufRateScale.kr(b1)* (modulator/440), 1, 0, 1)
	}.play;
) 

そしてこの章の最後のトピックは、再び「ベル」の音に戻ってくる。 これまでの例でも、ちょっとベルっぽい音はしたが、それはパーカッシブなエンベローブとか、金属の剛体振動によるインハーモニシティをちょっとだけ加味したものであった。 この例では気合いを込めまくり(^_^;)、「richer bell」を実現している。 派手さは無いが、こだわりのある こんな音がした。


(
	var numpartials, spectrum, amplitudes, modfreqs1, modfreqs2, decaytimes;  
	spectrum = [0.5,1,1.19,1.56,2,2.51,2.66,3.01,4.1];
	amplitudes= [0.25,1,0.8,0.5,0.9,0.4,0.3,0.6,0.1];
	numpartials = spectrum.size;
	modfreqs1 = Array.rand(numpartials, 1, 5.0); 
		//vibrato rates from 1 to 5 Hz 
	modfreqs2 = Array.rand(numpartials, 0.1, 3.0); 
		//tremolo rates from 0.1 to 3 Hz 
	decaytimes = Array.rand(numpartials, 2.5,7.5); 
		//decay from 2.5 to 7.5 seconds
	{
		Mix.fill(
			spectrum.size, 
			{
				arg i;  
				var amp, freq; 
				freq= (spectrum[i]+(SinOsc.kr(modfreqs1[i],0,0.005)))*500; 
				amp= 0.1* Line.kr(1,0,decaytimes[i])*(SinOsc.ar(modfreqs2[i],0,0.1,0.9)* amplitudes[i]);
				Pan2.ar(SinOsc.ar(freq, 0, amp),1.0.rand2)
			}
		);
	}.play
)

これで「3. Sound Synthesis 2」が終わりである。

2011年3月24日(木)

今日も午後まで事務仕事があり、わずかな時間だけとなってしまった。 Processing は今日も棚上げして、行けるところまでSuperColliderを進めることにしよう。 Workshop materials for Computer Music (G6002)4. SC Programming 14.1 Programming.html からである。サクサクと行きたいものだ。

どうやらここは「Syntax for SuperCollider as a Programming Language」ということらしい。 他の処理系と比較するにしても、サクサク行ってしまおう。 最初は以下のコメントである。ノーコメントである。


Comments

//this is a comment

/* this is also a comment */
次は、以下の「Brackets」である。これも既に出て来ているのでノーコメントである。 ネスティングは自在である。

(
	//my patch goes in here
)

(  )  //for grouping expressions together or 
	//passing arguments to methods, ie SinOsc.ar(440)

{  }  //function ie {arg a; a*a}

[  ]  //array (list of data) ie [1,2,3,4]
変数と値の割り当てのサンプルである。これはlocalhost serverを起動しなくても以下のように結果が出た。

(
	var a,b,c; //variables always get defined first in a block of code

	a=1; //assign some values to our variables
	b=a;
	c= b*5;
	[a,b,c].postln;

	a=4; //assign some new values to our variables
	b=a+9;
	c= c*5; //this is OK because c has a value already by now
	[a,b,c].postln;
)

変数には型の宣言が必要である。これはJavaと同じである。JavascriptやFlashのActonscriptでは型を厳密に宣言しなくても数値を代入すれば自動的にその型として対応してくれるのに比べて、いかにも古くて不親切である。しかしこれにより、無用なトラブルを避けているのである。JavascriptやFlashのActonscriptに馴れた「適当な人」は要注意である(^_^;)。 以下の例はOKである。


(
	var dave;
	dave= 5;
	dave.postln;
)

JavascriptやFlashのActonscriptのマナーで書いた以下の例は駄目な例である。 エラーが出て叱られる(^_^;)。


(
	dave= 5;
	dave.postln;
)

次は以下の「カプセル化(encapsulation)」である。SuperColliderはOOPSなので、データとそれを操作する手続きを一体化して「オブジェクト」として定義し、オブジェクト内の細かい仕様や構造を外部から隠蔽できる。 外部から公開された手続きを利用することでしかデータを操作できないようにすることで、個々のオブジェクトの独立性が高まる。 これによりオブジェクト内部の仕様変更が外部に影響しなくなり、ソフトウェアの保守性や開発効率が高まり、プログラムの部分的な再利用も容易になる。


(
	var myfunction;
	myfunction= {arg a; a*a}; 
		//define my own squaring function
	myfunction.value(8).postln; 
		//run my function with an input of 8 and post the result
)

以下の例を追いかけると、毎回「current=60; 」という値を設定して、まったく「同じArray.fill」を呼び出しているが、前回のcurrentをoldに格納してこれと加算しているので、毎回ランダムスケールが生成されている。


(
	var scale, current; //code for making a random scale

	current=60; 
	scale= Array.fill(8, {var old; old=current; current=current+rrand(1,3);  old}); 
		//construct for making an Array to a recipe- explained more in the next part
	scale.postln;

	current=60; 
	scale= Array.fill(8, {var old; old=current; current=current+rrand(1,3);  old});
	scale.postln;

	current=60; 
	scale= Array.fill(8, {var old; old=current; current=current+rrand(1,3);  old});
	scale.postln;
)

上の例は以下のようにも書ける。これでようやく、再帰的な呼び出し、というOOPSのメリットが出て来る。


(
	var makescale;
	makescale= {
		var current; 
		current=60; 
		Array.fill(8, {
			var old; old=current; current=current+rrand(1,3);  old;
		});
	};
	3.do({makescale.value.postln;});
)

次は「Looping」である。以下の「do」ループは、既に上に出ているのでノーコメント。


(
	20.do({"over and over".postln;})  
		//the function here is the thing to call each time round the loop
)

以下の「while」ループもお馴染みである。


(
	var n;
	n=0;
	while({n<5}, {n=n+1; "keep going".postln;})
)

以下の「for」ループもお馴染みである。ただしパラメータの順番がちょっと違っている(^_^;)。


(
	for(1, 5, {arg i;  i.postln; }); 
		//start at 1, step by 1 each time until you reach 5
)

以下の「forBy」ループというのは初めて見た。ステップ値を定義できるが、パラメータ数が増えているので、場合によっては混乱が予想される。


(
	forBy(1, 5, 2, {arg i;  i.postln; }); 
		//start at 1, step by 2 each time until you reach 5
)

次のトピックは「条件付き実行」である。 他の言語と同様に、SuperColliderでも基本的には、以下のように上から順に実行する。


(
	"me first!".postln;
	"now me!".postln;
	"don't forget me!".postln;
)

me first!
now me!
don't forget me!
don't forget me!
ここに「if」と確率1/2の「0.5.coin」を組み合わせると、以下のように実行したたびにランダムな結果となる。

(
	"me first!".postln;
	"now me!".postln;
	if(0.5.coin, {"don't forget me!".postln;},{"you forgot me!".postln});
		//roll a dice- heads we do one thing, tails the second 
)
me first!
now me!
you forgot me!
you forgot me!

me first!
now me!
don't forget me!
don't forget me!

me first!
now me!
you forgot me!
you forgot me!

me first!
now me!
don't forget me!
don't forget me!

me first!
now me!
don't forget me!
don't forget me!
「if」の定義は以下である。

if (logical test which returns true or false,  do this if true,  do this if false) 
そこで、実験として以下を実行すると様子がわかる。

if(true, "true!", "false!").postln;
true!

if(false, "true!", "false!").postln;
false!
以下も、実行した結果はちょっと考えると予測できる。

(
	var a,b;
	a=5;
	b= if(a==6, 11, 7);
	b.postln;
)

7
次のトピックは「Classes」である。クラスはカプセル化のために重要なツールであり、既に「SinOsc」、「SCWindow」、「MouseX」・・・など、たくさん出ているものである。 他のオブジェクト指向プログラミング言語と同様に、クラスの記述のしかたについて、SuperColliderは以下のようなルールがある。

まず、大文字で始まるのは「class name」である。 ここに以下のようにドットでメソッドを繋げることができる。


LFSaw.ar //ar is the method
postウインドウでこのメソッドの部分をハイライトして「command + y」で、このメソッドの解説がズラズラと出て来る。 「ar」の解説はとんでもなく長いので別ファイルにした。 これである。

「.dumpClassSubtree」というメソッドをひっつけて実行すると、その上のクラス(superclass)の解説を表示できる。 ここでは


UGen.dumpClassSubtree
というのを実行させたが、そのpostウインドウの中身はとんでもなく長いので別ファイルにコピペした。 これである。

すべてがObjectのクラスに由来しているので、「Object.dumpClassSubtree」とすることで、全体の階層構造を見ることが出来る。 新しいクラスを追加したい場合には、それを「.sc」という拡張子のファイルにして、「SCClassLibrary folder」に置いた上で、「command + k」によってリコンパイルすればよい。

これで「4.1 Programming.html」は終わったので、次は 4. SC Programming 14.2 Arrays.html である。配列「Arrays」である。 SuperColliderは多数のクラスの集合であるが、簡単な例では従来の言語と同様に、以下のように直接的に「[」「]」ブラケットで囲んだデータの配列を定義できる。


a= [5,6,7,8,9,10];
もう少し複雑な場合には、以下のように関数を使って配列を生成することも出来る。

a= Array.fill(6, {arg i;  i+5});  
配列の生成にはいくつかのトリッキーな(有用な)方法がある。 「Array.series」は順番に並ぶ要素の配列である。 以下の2つの例は実行してみると判るように同等である。

Array.series(10,1,1) 
	//arguments to series are number of elements, start element, and add
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

(1..10) 
	//this is a shortcut for the same thing
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
以下の2つの例も実行してみると判るように同等である。

Array.series(10,1,2) 
[ 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 ]

(1,3..20) //again a shortcut
[ 1, 3, 5, 7, 9, 11, 13, 15, 17, 19 ]
上の例はいわば「等差数列」を要素とする配列を生成するが、「Array.geom」は等比数列を要素として生成する。 以下のように、一気に増大するので注意が必要である。

Array.geom(10, 1, 1.1); 
	//geometric rather than arithmetic series: arguments number of elements, start element, grow ratio
[ 1, 1.1, 1.21, 1.331, 1.4641, 1.61051, 1.771561, 1.9487171, 2.14358881, 2.357947691 ]

Array.geom(10, 1, 10); 
	//be careful, the law of compound interest can make these numbers expand very fast! 
[ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 ]
「Array.rand」はランダム要素を指定した数だけ並べた配列を生成する。以下のように乱数の値域を指定できる。

Array.rand(10, 0.7,2.4); 
//make 10 random numbers drawn from a linear distribution between 0.7 and 2.4

[ 2.3675505518913, 1.2208888173103, 1.3846839427948, 1.8373642921448, 
2.3120567083359, 1.8555413007736, 1.0854766488075, 1.4676449418068, 
1.1732110023499, 1.8694380164146 ]

[ 1.1125028252602, 1.7578379034996, 0.91249594688416, 1.9855660676956, 
1.8772662043571, 1.4862836003304, 1.0565153121948, 1.3792539834976, 
0.96189289093018, 1.2802971601486 ]

[ 0.7542892575264, 1.6704448223114, 1.5092353820801, 0.7836030125618, 
1.5309412360191, 1.210222029686, 0.99778627157211, 2.2023021817207, 
1.4410777807236, 1.0018934965134 ]
配列の要素全てに対して、以下のようにいろいろな処理が一気に行える。これはなかなか面白い。

a = [1,3,5,6];
[ 1, 3, 5, 6 ]

a+5
[ 6, 8, 10, 11 ]

a*8
[ 8, 24, 40, 48 ]

a**2 //take to power of 2
[ 1, 9, 25, 36 ]

a.squared //same thing
[ 1, 9, 25, 36 ]

a.sqrt
[ 1, 1.7320508075689, 2.2360679774998, 2.4494897427832 ]

a.scramble //run me multiple times!
[ 6, 5, 3, 1 ]

a.rotate(1)
[ 6, 1, 3, 5 ]

a.rotate(-1)
[ 3, 5, 6, 1 ]

a-10
[ -9, -7, -5, -4 ]

(a-10).abs
[ 9, 7, 5, 4 ]

a>4
[ false, false, true, true ]

a.reverse
[ 6, 5, 3, 1 ]
配列の個々の要素をインデックスを使って指定する方法は以下のようなものがある。 最後には「crashするからやめれ(^_^;)」というのもあるが実行してみた。

b=[2,3,4,7]
[ 2, 3, 4, 7 ]

b.at(3) 
	//get the element at index 3 (meaning the fourth element)
7

b.at(4) 
	//won't return anything because the array isn't 
	//big enough! nil is a placeholder for 'no response 
	//possible' and will lead to trouble when it crops up
nil

b.put(2,50) 
	//put 50 into the slot at index 2 (replace third element)
[ 2, 3, 50, 7 ]

b 	//note that b itself has been changed
[ 2, 3, 50, 7 ]

b.put(14,90) 
	//crash- won't work, no space to put the 90 in, 
	//array only has four element slots

ERROR: Primitive '_BasicPut' failed.
Index out of range.
RECEIVER:
Instance of Array {    (1764A5B0, gc=CC, fmt=01, flg=00, set=02)
  indexed slots [4]
      0 : Integer 2
      1 : Integer 3
      2 : Integer 50
      3 : Integer 7
}
CALL STACK:
	MethodError:reportError   15E4D0B0
		arg this = ...
	Nil:handleError   1B531090
		arg this = nil
		arg error = ...
	Thread:handleError   1B52D670
		arg this = ...
		arg error = ...
	Object:throw   15E3EE30
		arg this = ...
	Object:primitiveFailed   15E59350
		arg this = [*4]
	Interpreter:interpretPrintCmdLine   16745810
		arg this = ...
		var res = nil
		var func = ...
		var code = "b.put(14,90) //crash- won't ..."
		var doc = ...
	Process:interpretPrintCmdLine   1B5340F0
		arg this = ...
Javaと同様に、以下のようにも記述できる。

b=[2,3,4,7]
[ 2, 3, 4, 7 ]

b[0] //get first element
2

b[0]= 74 //set first element
[ 74, 3, 4, 7 ]

b //was changed
[ 74, 3, 4, 7 ]
「[1,2,3] 」のように表記する通常の配列(要素の書き換えが出来る)に対して、「#[1,2,3]」のように表記した配列がある。 これはSuperColliderの効率化のために、定数データを格納した配列で、要素は参照するだけ、というものである。 両方を比較した以下の実例で様子が判る。

[1,2,3] //a dynamic array 
[ 1, 2, 3 ]

a[0] //works
1

a[0] = 8 //works
[ 8, 3, 5, 6 ]

a= #[1,2,3];
[ 1, 2, 3 ]

a[0] //works
1

a[0] = 8 //fails, because it can't be changed

ERROR: Primitive '_BasicPut' failed.
Attempted write to immutable object.
RECEIVER:
Instance of Array {    (1764A810, gc=00, fmt=01, flg=10, set=02)
  indexed slots [3]
      0 : Integer 1
      1 : Integer 2
      2 : Integer 3
}
CALL STACK:
	MethodError:reportError   15E55B70
		arg this = ...
	Nil:handleError   15E56050
		arg this = nil
		arg error = ...
	Thread:handleError   15E56230
		arg this = ...
		arg error = ...
	Object:throw   15E566B0
		arg this = ...
	Object:primitiveFailed   15E569B0
		arg this = [*3]
	Interpreter:interpretPrintCmdLine   1764EF10
		arg this = ...
		var res = nil
		var func = ...
		var code = "a[0] = 8 //fails, because it..."
		var doc = ...
	Process:interpretPrintCmdLine   15E56B30
		arg this = ...
もう少し複雑な配列としては、UGenが要素になる配列、というのもある。 以下の例では4つのUGenを定義して鳴らしいてる。 しかしパソコンがステレオなので最初の2チャンネルだけが鳴り、 こんな音がした。

( 
		//you'll only hear the first two of four frequencies 
		//if you have just a stereo output
	{
		var freqs,array;
		freqs= [440,443,447,455.7];
		array=Array.fill(
			4, //4 elements will go into this array
			{
				arg i; //this function is the recipe to make each element
				SinOsc.ar(freqs.at(i), 0, 0.1)
			}
		);
		array
	}.play
)

これを「Mix」でモノラルに混ぜると、4音のミックスとなって こんな音がした。


(
	{
		var freqs,array;
		freqs= [440,443,447,455.7];
		array=Array.fill(
			4, //4 elements will go into this array
			{
				arg i; //this function is the recipe to make each element
				SinOsc.ar(freqs.at(i), 0, 0.1)
			}
		);
		Mix(array)
	}.play
)

「Mix」と「fill」を合体させたショートカット「Mix.fill」を使うと、以下のように、まず「Mix」で4音をミックスしたものを4つ持つ配列が出来て、しかしパソコンがステレオなので最初の2チャンネルだけが鳴り、 こんな音がした。


(
	//you'll hear the first two of four frequencies if you only have a stereo output
	{
		var freqs,array;
		freqs= [440,443,447,455.7];
		Mix.fill(
			4, //4 elements will go into this array
			{
				arg i; //this function is the recipe to make each element
				SinOsc.ar(freqs.at(i), 0, 0.1)
			}
		);
	}.play
)

これで「4.2 Arrays.html」は終わったので、次は 4. SC Programming 14.3 SynthDefs.html である。SuperColliderの中核、「SynthDefs (Synthesizer Definition)」である。

これまで多くの例で「{}.play」としてサウンドを鳴らしてきたが、granularとかensembleとかの複雑なサウンドを鳴らすには、多数のUnit Generatorsを組み合わせるためにSynthDefsを使うのが本命である。 今回のSuperColliderでは、OSCとかGainerとかArduinoとかで外部から制御するサウンドサーバとしたいので、いちいちSuperColliderのpostウインドウで選択してenterする、というのでなく稼働させたいので、これは重要である。

まずは実例である。たった1行、まず以下を実行する。


SynthDef(\sine, {Out.ar(0,SinOsc.ar(Rand(440,880),0,0.1))}).add;
これで、「\sine」という名前のシンセが定義される。 あとは、

Synth(\sine); 
とすると、その音が鳴る。どんどん起動していくと、以下のようになり、遂には音が割れてきて、 こんな音がした。

上の例が、これまでの「{SinOsc.ar(440,0,0.1)}.play」というのとどう違うか、は以下である。 たったこれだけで、起動するたびにどんどん面白い音になっていく。

また、上の例では単に「Synth(\sine); 」としたので、重なった音のどれかを後で消すことは出来ないが、以下のようにすれば個別に消してメモリを解放できる。

a=Synth(\sine);  
b=Synth(\sine);  
c=Synth(\sine); 

a.free;
b.free;
c.free;
SynthDefの名前の書式には「\aaa」の他に、以下のように「"aaa"」も使える。 バックスラッシュは出ないこともあるので、こっちの方が良さそうである。

SynthDef("aaa",{Out.ar(0,SinOsc.ar(440,0,0.1))}).add

Synth("aaa"); //now this
SynthDefの出力はdefault Serverに送られる。サーバが起動されていないと音は出ないが静かに無視される(^_^;)。 これはlocalhost serverの緑色の「-> default」でも確認できるが、postウインドウでも以下のように確認できる。

Server.default 
		//should return the server, usually localhost or internal; 
		//global variable s also typically points to the same 
localhost

s.serverRunning 
		//check if it is on; should return true
true
この後に「warning」として古いバージョンのSuperColliderに関する記述があったが、ここは軽くスルーした(^_^;)。 「.add」については、以下のように「.writeDefFile」と「.store」との違いを覚えておきたい。 SynthDefは、それ自体の名前を呼び出して発音するだけでなく、内部で定義されているパラメータを指定して発音させることが出来る。以下の例では、まずdefaultで発音し、さらに周波数を変えて発音している。 こんな音がした。

SynthDef(\sine,{arg freq=440, amp=0.1; Out.ar(0,SinOsc.ar(freq,0,amp))}).add;
a SynthDef

Synth("sine");
Synth("sine" : 1001)

Synth("sine",[\freq,880]);
Synth("sine" : 1002)

以下の例では、まず「a」としてdefaultで発音し、さらに周波数を変えて「b」として発音し、さらに周波数と振幅を変えて「c」として発音している。その後、「a」を残したまま「c」の、さらに「b」のパラメータだけ変更している。そして最後に「a」「b」の順に消音(free)している。 こんな音がした。


SynthDef(\sine,{arg freq=440, amp=0.1; Out.ar(0,SinOsc.ar(freq,0,amp))}).add;
a SynthDef

a=Synth(\sine);  
Synth("sine" : 1001)

b=Synth(\sine,[\freq,550]);  
Synth("sine" : 1002)

c=Synth(\sine,[\freq,660, \amp, 0.5]); 
Synth("sine" : 1003)

c.set(\freq, 1000);
Synth("sine" : 1003)

b.set(\amp, 0.3, \freq, 100)
Synth("sine" : 1002)

a.free;
b.free;

最後に「Exercise」としてあったのは、以下のようなテンプレートである。 ここに色々なものを入れて、オリジナルのSynthDefが作れる、というものである。


(
	SynthDef(
		\synthdefname,
		{
			arg input1= defaultvalue; 
				//any arguments go here, make 
				//sure they have default values
				//some code for UGens - the 
				//sort of thing that went 
				//inside {}.play before
			Out.ar(0, finaloutput) 
				//finaloutput is the final 
				//result UGen you want to hear
		}
	).add
)

Synth(\synthdefname, [\input1, inputval1]); 
	//inputval1 is the constant starting value for argument input1
これで「4.3 SynthDefs.html」は終わったので、次は 4. SC Programming 14.4 Exercises.html である。 ここには4つの「問題」が示されているが、セミナーでは「来週に回答を提示して議論しよう」とあった。 その「来週」は次のセクションなので、ここでオシマイとした(^_^;)。 ここまでを「日記(2)」として区切りにして、次は「日記(3)」として 5. Interaction から始めてみたいと思う。

SuperCollider日記(3)

「日記」シリーズ の記録