エンベロープ

以前UGenの説明なんかやってられっか、と書きましたが
音作りの基本的に押さえといたらいいんではないか、というのはいくつかある。
例えばEnvGenはエンベロープを生じせしめるものですが、エンベロープが何かというと、音量の変化と言うか、音の輪郭と言うか。
現実の音には必ず始めと終わりがありますが(エネルギーは散逸する)
データとしての音にそんなものはないので、必要なら始めと終わりを与えてやらねばならない。

SynthDef("hoge", {
var
o = SinOsc.ar(500);
Out.ar(0, Pan2.ar(o, 0));
}).play;

これはただの「ぽー」です。

SynthDef("hoge", {
var
e = EnvGen.ar(Env.perc(0, 1)); // エンベロープ
o = SinOsc.ar(500, 0, e);
Out.ar(0, Pan2.ar(o, 0));
}).play;

最大から始まり1秒で消え去るエンベロープを入れました。
これだけでも、ただの「ぽー」ではなく楽器っぽい感じになる。
EnvGenとかEnvの詳細はヘルプを参照(丸投げ)。

SynthDef("hoge", {
var
t = Impulse.ar(1); // 1Hzのインパルス
e = EnvGen.ar(Env.perc(0, 1), t);
o = SinOsc.ar(500, 0, e);
Out.ar(0, Pan2.ar(o, 0));
}).play;

Impulseでもってトリガさせたりも可能。
エンベロープと言っても、要は音と同じ波形なので、用法は何も音量に限らない。

SynthDef("hoge", {
var
t = Impulse.ar(1);
e = EnvGen.ar(Env.perc(0, 1), t);
o = SinOsc.ar(500*e, 0, e); // 周波数にエンベロープ
Out.ar(0, Pan2.ar(o, 0));
}).play;

周波数にエンベロープを掛けたら「どぅーん」という下がる音になった。

SynthDef("hoge", {
var
t = Impulse.ar(1);
e = EnvGen.ar(Env.perc(0, 1), t);
o = SinOsc.ar(e**2*500, 0, e); // 周波数にエンベロープの2乗
Out.ar(0, Pan2.ar(o, 0));
}).play;

何乗というのは「**」と書く。エンベロープの2乗って何だ。
二次関数のグラフ、いわゆる放物線というやつですが、こうすると周波数がぎゅいんと下がってセクシーな感じになる。と思います。数字を増やせばどんどんくびれる。

スコアとレンダリング

疲れるのは嫌なので、もっとこう、マシンぽく自動的にできないものか、ということでした。

スコア

x = [
[0.0, [ \s_new, \hoge, 1000, 0, 0, \nn, 60 ]],
		// 0.0秒後、ノード1000番としてhogeを立ち上げる。nnは60
[0.5, [ \n_set, 1000, \nn, 62 ]],
		// 0.5秒後、1000番のnnは62
[1.0, [ \n_set, 1000, \nn, 64 ]],
[1.5, [ \n_set, 1000, \nn, 65 ]],
[2.0, [ \n_set, 1000, \nn, 67 ]],
[3.0, [ \n_free, 1000]],
		// 3.0秒後、1000番を片付ける
[3.0, [ \c_set, 0, 0]],	
		// 3.0秒後、おわり
];

このスコアというのを書くと、シンセの立ち上げや引数の切り替えを時間に沿ってスケジュールできる。
音を並べることが作曲だとすれば、既にその段階に入りつつあると。
然る後に以下を実行。

Score.play(x);		// xをスコアとして演奏

いやー良い曲だ。
ノードナンバーというのは、同じ名前のシンセを複数立ち上げて、個別の番号で管理できる。
例えばこう。ちなみに時間は順不同に書いても大丈夫らしい。

SynthDef("hoge", {
	arg nn;
	var a, b;
	a = SinOsc.ar(nn.midicps);
	b = Pan2.ar(a, 0, 0.5);
	Out.ar(0, b);
}).store;

x = [
[0.0, [ \s_new, \hoge, 1000, 0, 0, \nn, 60 ]],
[0.5, [ \n_set, 1000, \nn, 62 ]],
[1.0, [ \n_set, 1000, \nn, 64 ]],
[1.5, [ \n_set, 1000, \nn, 65 ]],
[2.0, [ \n_set, 1000, \nn, 67 ]],

[0.0, [ \s_new, \hoge, 1001, 0, 0, \nn, 64 ]],
[0.5, [ \n_set, 1001, \nn, 65 ]],
[1.0, [ \n_set, 1001, \nn, 67 ]],
[1.5, [ \n_set, 1001, \nn, 69 ]],
[2.0, [ \n_set, 1001, \nn, 71 ]],

[3.0, [ \n_free, 1000]],
[3.0, [ \n_free, 1001]],
[3.0, [ \c_set, 0, 0]],	
];

Score.play(x);

スコアを途中で止めたい場合があるとしますと、以下のようにします。

y = Score.new(x);
y.play;	// 演奏開始
y.stop;	// 演奏中止

レンダリング

スコアが書けるといよいよ録音というか、レンダリングが可能になります。
これのコードは多少ぐちゃぐちゃしてるので、何も考えんとコピペ。

o = ServerOptions.new.numOutputBusChannels = 1;
Score.recordNRT(x, "hoge.osc", "hoge.wav", // xをレンダリング
headerFormat: "WAV", sampleFormat: "int16",
options: o);

スコアをレンダリングということで、先にスコアの部分を実行しておく。
本体フォルダにhoge.wavができました。素晴らしい。
他の形式も可能。例えばこれを24bitのステレオにしたければ

o = ServerOptions.new.numOutputBusChannels = 2;
Score.recordNRT(x, "hoge.osc", "hoge.wav",
headerFormat: "WAV", sampleFormat: "int24",
options: o);

これで音声ファイルが作れるようになってしまいました。
ものによるでしょうが、概ね実際の演奏時間より早く書き出せるのは何かと便利。

SynthDef

箇条書き(と言うのかわからないけど)を使っていろいろ音作りができそうなことはわかった。
これをSynthDef(「シンセの定義」だろうか)というものの中に入れると、名前をつけたり、出し入れできたり、ひとつの音源のように扱うことができる。

SynthDef("hoge", {		// hogeなるシンセなり
	var a, b;		// a、bなる変数あり
	a = SinOsc.ar(440);
	b = Pan2.ar(a, 0);
	Out.ar(0, b);		// バス0番にbを出力
}).play;

ところでコメントは // または /* */ よくある感じ。
コメント部分は無視されるので何を書いても良い事になっている。日本語も可のはず。
playすると音が出るのはこれまで同様。

SynthDef("hoge", {
	var a, b;
	a = SinOsc.ar(440);
	b = Pan2.ar(a, 0);
	Out.ar(0, b);
}).store;

今度はplayではなくstoreとなっておる。
この場合は直ちに音出しするのではなく、SynthDefファイルをどこかに保存している。

Synth("hoge");		// 立ち上がれhoge

これで呼び出せる(音が鳴る)。好きな時に出し入れできるようになりました。

引数

SynthDef("hoge", {
	arg nn;				// nnなる引数あり
	var a, b;
	a = SinOsc.ar(nn.midicps);	// 周波数はnn.midicps
	b = Pan2.ar(a, 0);
	Out.ar(0, b);
}).store;

引数というのを使ってシンセに好きな数字をくれてやることができる。

Synth("hoge", [\nn, 60]);	// 立ちやがれhogeよ、あとnnは60でね

中央のドで鳴ります。

Synth("hoge", [\nn, 67]);

その上のソで鳴ります。というふうに使い分けられる。
setを使って途中で値を変えることもできる。

// ひとつずつ実行
x = Synth("hoge");	// xとしてhogeを立ち上げる
x.set(\nn, 60);	// xのnnは60
x.set(\nn, 67);	// xのnnは67
x.free;		// xを片付ける 

立て続けにブチブチ押せば、それなりに演奏的なこともできそうだと。
ただし疲れる。これを次回何とかします。

計算

関数

randとかmidicpsというのは関数だろう。
(Operatorsヘルプによると単項演算子二項演算子という言い方なんだが)
関数とは、ある数をくれてやると、計算した結果を返してくれるというやつ。

60.midicps.postln;

関数のふるまいを手っ取り早く見るにはpostlnで文字を出力。
(postは改行なし、postlnは改行つき)
これをCtrl+Enterしますと、Post Windowに

261.6255653006
261.6255653006

大事なことだから二度言うのか知りませんが、60.midicpsの答えが出た。
ちなみに、

60.midicps.postln;
midicps(60).postln;
postln(midicps(60));

どう書いても同じ意味で通じる。
総じてこういう文法なのだが、これを便利と言うのかわかりません。
さて、midicpsというのは「midiノートナンバーを周波数に変換」する関数。
midiノートナンバーって何だ?
うーむいきなり例が悪かった。簡単に言うと、この数字はピアノの鍵盤みたいなもので、60は確か中央のド。59はその隣のシ。
ということはこの人によれば、中央のドとは261.6255653006Hzである。勉強になります。

10.rand.postln;

randは乱数だから毎回違う数字をデタラメに返す。
この場合、10までではなく、0〜9の整数。

10.0.rand.postln;

こうすると小数を返すようになる。
他にも関数いろいろ多数。詳しくは各ヘルプを見よ(丸投げ)。

計算の順序

(2+2/2).postln;
(2+(2/2)).postln;

上と下で答えが違います。えー
掛け算や割り算を足し算や引き算に優先させるという考えがないらしいので、カッコで計算順序を明示してやらねばならない。
しかし左から順に淡々と計算するということでは、時々便利な場合もあります。

Mixの用法

{ SinOsc.ar([400, 500]) }.play;

こう書くと左右別々に鳴らせる。ほほー

{ SinOsc.ar([400, 500, 600]) }.play;

三つ同時は無理だったもよう。右の次というのはないもんな

{ Mix(SinOsc.ar([400, 500, 600])) }.play;

Mixで三つ同時に鳴らせる。ただしこれだと音量も三倍で割れてる。
つまりこの書き方で、実際にはSinOscが三つ立ち上がってるということか。
あと、またしても左だけになってる。

{
a = Mix(SinOsc.ar([400, 500, 600]));
b = Pan2.ar(a, 0, 1/3);
}.play;

真ん中に持ってきて音量を1/3にした。
こうしてきれいな三和音ができました。めでたし。
別に三つに限らず何個でもMixできる。ついでに配列の部分を外に出して

{
a = [100, 200, 300, 400, 500, 600, 700];
b = Mix(SinOsc.ar(a));
c = Pan2.ar(b, 0, 1/a.size);
}.play;

sizeというのは配列の長さの意味。この場合a.sizeとは7だから、音量は1/7になってる。
配列の長さによらず常に音量の合計を1にする作戦です。

{
a = 8;
b = Mix.fill (a, {
		c = 1000.rand;
		d = SinOsc.ar(c, 0, 1/a);
});
e = Pan2.ar(b, 0);
}.play;

やや。これまた見慣れぬ感じになりましたが、今度はMix.fill。
{ }の間の処理を繰り返すものでして、aで繰り返しの回数を指定。
randについては後述。

{
a = 8;
b = Mix.fill (a, {
		arg n;
		c = 100*n;
		d = SinOsc.ar(c, 0, 1/a);
});
e = Pan2.ar(b, 0);
}.play;

今度はMix.fillの中にargというのが入っている。
するとどういうわけか、繰り返す度にこれの値が1ずつ増える。
出音としては二つ前のと同じと思われます(最初に0HzのSinOscが混ざっちゃってるが)

{
a = 8;
b = Mix.fill (a, {
		arg n;
		c = (5*n+50).midicps;
		d = SinOsc.ar(c, 0, 1/a);
});
e = Pan2.ar(b, 0);
}.play;  

さっきよりは和音っぽい感じですが、今度はmidicpsというのを使ってる。midicpsについても後述。

箇条書き

{ SinOsc.ar(SinOsc.ar(1, 0, 1000)) }.play;

入れ子でもずらずら書けるんだけど、見にくいよね。

{
a = SinOsc.ar(1, 0, 1000);
b = SinOsc.ar(a);	// 周波数をaにする
}.play;

さっきと同じ意味だが、こんな感じに分解して書ける。
箇条書き、と言うのかわかりませんが、だいぶ見やすくなった。
また変数を再利用するつもりでなければこうも書ける。

{
a = SinOsc.ar(1, 0, 1000);
a = SinOsc.ar(a);
}.play;

それにしてもさっきから音が左に寄っててきもすけわり

{
a = SinOsc.ar(1000);
b = Pan2.ar(a, 0);	// 定位を中央(0)にする
}.play;

真ん中になった。何故かというのはPan2のヘルプを見よ。
(追記:定位を中央にするだけなら以下の書き方でもありだそうです)

{
a = SinOsc.ar(1000);
b = [a,a];	// 左右に並べる
}.play;

// または
a = SinOsc.ar(1000);
b = a!2;	// 上と同じ意味
}.play;

いたずらしてPan2にSinOscを入れてしまいました。

{
a = SinOsc.ar(1000);
b = SinOsc.ar(1);
c = Pan2.ar(a, b);	// 定位をbにする
}.play;

左右に動く。
こうなればもう何でもありじゃ

{
a = SinOsc.ar(3, 0, 1000);
b = SinOsc.ar(1);
c = SinOsc.ar(a, 0, b);
d = SinOsc.ar(0.3);
e = Pan2.ar(c, d);
}.play;

いきなりわけわからんコードになった。
何やら妙に緊迫した雰囲気の出音。

{
a = SinOsc.ar(400);
b = SinOsc.ar(500);
c = a + b;	// aとbを足す
d = Pan2.ar(c, 0, 0.5);
}.play;

SinOscどうしを足す?と思ったら、単に2つの音が混ざる。
音データは数字だから、計算できる。って書くと当たり前なようだが、やっぱり面白い。
振幅1の音を2つ足したから振幅の合計が2になった。ゆえにPan2で半分にしてあげる。

{
a = SinOsc.ar(400);
b = SinOsc.ar(10);
c = a * b;	// aとbを掛ける
d = Pan2.ar(c, 0);
}.play;

掛け算でも音量の変化が作れる。慣れるとこの方がわかりやすい。

{
a = SinOsc.ar(1, 0, 1000);
b = SinOsc.ar(a);
c = SinOsc.ar(500);
d = b * c;
e = Pan2.ar(d, 0);
}.play;

これまた掛け算ですが、今度はなんかゴエゴエ言ってる。
音どうしを掛けるのはリングモジュレーションと呼ばれるが、可聴域内の2音を掛けるとこういう謎の音色になる。
モジュレーションによる音作りにもいろいろあるようだが、それはともかく、箇条書きでいろんな事ができそうだとわかった。

波形など

入れ子

またまたWin32Tests.sc

{ SinOsc.ar(LFNoise0.kr([8, 12], 200, [300, 400]), 0, 0.1) }.play;

これというのはもしかして、SinOscの中にLFNoise0が入れ子になってるっていう。
気になったらすぐ書いて試してみる。

{ SinOsc.ar(1000, 0, SinOsc.ar(1)) }.play; 

SinOscの中にSinOsc。mulのところに入ってる。音量が変わるので、mulとは音量のことか。

{ SinOsc.ar(1000, 0, SinOsc.ar(10)) }.play; 

周波数を上げたらうわんうわんうわんがうろろろろとなった。

波形をみる

音のデータというのは、要は数字であって、数字で波形が書いてある。確か。
playではなくplotとしてやると、波形がグラフで見られる。

{ SinOsc.ar(1000) }.plot;

このように-1と1の間で波打ってるので、これをmulに当ててやると、音量がうわわわわとなる。

{ SinOsc.ar(10) }.plot;

倍率を変えると見やすいかも。0.1秒間で描画。

{ SinOsc.ar(10) }.plot(0.1);

10Hzのサイン波ですが、これ単体だと聴こえません。
音が低すぎる。というか波が遅すぎる。
これをさっきみたいに1000Hzのサイン波の中に入れてやると。

{ SinOsc.ar(1000, 0, SinOsc.ar(10)) }.plot(0.1);

10Hzのサイン波が音量の変化として聴こえてくることになる。LFOというやつですね。
ちなみに音量とは感じ方の話であって、正確には波形の振幅と言う。
つまり音のデータとは振幅データである。確か。
波の振れ幅が大きいと音量は大きくなるが、これが1を超えると音が割れる
振幅データは-1〜1の範囲内の数字しか使えない。
±1より大きい数字を出そうとするとどうなるかというと、単に波形がちょん切られる。ゆえに波形が壊れる。

{ SinOsc.ar(100, 0, SinOsc.ar(0.3, 0, 10)) }.plot(0.1);

波が壁にぶち当たって変形しておりますね。
これを音で聴くとどうなるか。波形が壊れると言っても別にPCが壊れるわけではない。

{ SinOsc.ar(100, 0, SinOsc.ar(0.3, 0, 10)) }.play;

何となく音色が変わったような。
ところでplotしてるうちにPost Windowが挙動不審になるかも知れません。
その場合、ある方によれば「SwingOSC.scというファイルを探し出し、deathBounces = 4をdeathBounces = 20とかに書き換えてやると良い」そうです。
なぜ良いのかは不明。
他にもGUI周りが未実装なのか動かないとか、何かと謎多きPsyColliderですが、感謝の心を胸に我々は前に進みませう。

{ SinOsc.ar(SinOsc.ar(0.5, 0, 1000)) }.play; 

今度は周波数のところにSinOscを入れてみた。
この場合は-1〜1だと大した変化がないので、中のSinOscの振幅を1000倍している。
周波数は直接音として出てくるわけではない(?)ので-1〜1以上でも大丈夫なのである。
つまり-1000〜1000Hzを往復しているわけだが負の周波数て何やねんと思うかも知れない。
負の周波数は波形が裏返しになるが、音の高さは変わらない。
こんどはscopeで見てみます。

{ SinOsc.ar(SinOsc.ar(0.5, 0, 1000)) }.scope; 

波形がバネみたいにのびたりちぢんだり。
波形って本当に面白いですね。
ところで

s.scope;

とやると現在の出音を見ることができるらしい。