pdをやりなおす(1)

参考

オーディオプログラミング言語の比較
http://en.wikipedia.org/wiki/Comparison_of_audio_synthesis_environments


開発者Miller Puckette氏サイト http://crca.ucsd.edu/~msp/
電子音楽の理論とテクニック」http://crca.ucsd.edu/~msp/techniques.htmが参考になるかもしれない。例をpdで挙げている。

導入

pd-extendedをダウンロードする。入手先 http://puredata.info/downloads
各種拡張ライブラリが予め含まれており、インストーラで導入できるので便利。

音声のテスト

無事pdのダウンロードとインストールが完了、起動したものとする。


Media>Test Audio and MIDI、テストパッチを開く。
「TEST SIGNAL」ラジオボタンを押してみて(-40または-20)音が聴こえればOK。
聴こえない場合はPCを含めオーディオ設定を調べる。
オーディオ処理を示すcompute audioのチェックボックスにチェックがあることを確認。
(通常は自分でチェックを入れる)


以降は最大音量が出るのでオーディオ設定に注意して次へ。

基本操作

File>New、新規パッチウィンドウを開く。
pdでは、ここに各種オブジェクトを並べ、線でつなぐことでプログラミングをする。


Put>Object、新規オブジェクトを置く。
箱の中に「osc~」とタイプし、箱の外で1回クリック。
osc~オブジェクトを作成した。これはオシレータである。
もうひとつオブジェクトを作成する。今度は「dac~」オーディオ出力。


pdにどんなオブジェクトが用意されているのかを知るには、ウィンドウ内の何もない場所で右クリック>Help。
標準オブジェクトの一覧が見られる。
オブジェクトを右クリック>Helpでオブジェクトのヘルプが見られる。


音声信号を扱うオブジェクトは多くの場合、名前の末尾が~(チルダ)になっている。
例えば掛け算を行なうオブジェクトには*と*~の2種がある。


さて、先程のosc~の左下角にポインタを移動すると、ポインタが○の形になる。
そのままdac~の左上角までポインタをドラッグすると線が伸び、再びポインタが○になる。
ここでマウスボタンを離すと太線が引かれる。太線は音声の経路を意味する。
なお、数値等の値を扱うオブジェクト(チルダなし)には音声信号は入力できない(結線できない)。


オブジェクトにはこのように端子の印が描かれている。
上側にあるのは入力(インレット)、下側にあるのは出力(アウトレット)。


dac~のもうひとつのインレットにも太線を引く。
線を消すにはEditモードで線の上にポインタを置く→×になるのでクリック→Edit>Cut。


キーボードショートカットは各自確認すること。


さてオシレータをオーディオ出力につないだが、このままでは音が聴こえない。
Put>Number、ナンバーボックスを作成。
ナンバーボックスのoutからosc~の左インレットに線を引く。
すると今度は細線が引かれる。細線は数値等の値の経路を意味する。


Edit>Edit mode、演奏モードに切り替える。
ナンバーボックスをクリックし「100」とタイプすると100Hzのサイン波が出る。
ナンバーボックスをドラッグすると数が連続的に変化する。
Shift+ドラッグでナンバーボックスの増減は0.01刻みになる。


以上、pdで音を出すための最も簡単なパッチ(プログラム)を書いた。
File>Save、適当な名前で保存する。


保存したpdファイルをテキストエディタで開いてみる。
(ファイルの場所は通常C:\Program Files\pd)

#N canvas 0 0 450 300 12; #X obj 200 134 osc~; #X obj 201 176 dac~; #X floatatom 200 96 5 0 0 0 - - -; #X connect 0 0 1 0; #X connect 0 0 1 1; #X connect 2 0 0 0;


pdのコードはこのようにテキストで記述されている(改行は省略した)。
以後、作例はこのようにテキストで示す。
空のテキストファイルにコードを貼り付け、拡張子pdで保存。pdから開いて実行する。

音量を操作する

めでたく音が出たとはいえ、実はこのままでは具合が悪い。
pdウィンドウのpeak metersチェックボックスを入れてみると、OUTの値が100となり、CLIPが赤く点灯している。
osc~の値が20未満で可聴域を下回っていても、常に信号は出ている状態。

#N canvas 0 0 450 300 12; #X obj 156 190 dac~; #X obj 157 80 osc~ 440; #X obj 157 138 *~; #X floatatom 249 80 5 0 0 0 - - -; #X connect 1 0 2 0; #X connect 2 0 0 0; #X connect 2 0 0 1; #X connect 3 0 2 1;


音量を操作するにはosc~の出力に*~をつなげて掛け算を行なう。
ナンバーボックスを操作することで音量を変えられるが、Shift+ドラッグで0.01刻みにする必要がある。
試しに1以上の値にするといきなり音が割れてしまう。
osc~の出力は-1〜+1の範囲。これはpdが正常に扱える音声信号の範囲に等しい。


さらにナンバーボックスをドラッグするたびに(よく聴くと)ブチブチとノイズが乗る。
これは数値などが音声信号よりも粗い時間間隔でやりとりされるためだろう。


なお、osc~の横に440とあるのは引数。引数で周波数の初期値を指定することもできる。
この場合は440Hzのオシレータとなる。

#N canvas 0 0 454 323 12; #X obj 177 165 dac~; #X obj 119 50 osc~ 440; #X obj 178 113 *~; #X floatatom 267 50 5 0 0 0 - - -; #X obj 267 -98 vsl 15 128 0 1 0 0 empty empty empty 0 -9 0 10 -262130 -1 -1 0 1; #X obj 196 50 lop~ 5; #X connect 1 0 2 0; #X connect 2 0 0 0; #X connect 2 0 0 1; #X connect 4 0 3 0; #X connect 4 0 5 0; #X connect 5 0 2 1;


改良したパッチ。スライダで音量を操作する。
スライダのプロパティで出力範囲を0〜1に変更(初期状態は0〜127)。
(オブジェクトのプロパティは右クリック>Properties)


また、操作時のノイズを防ぐため、スライダの値をローパスフィルタlop~に入力している。
カットオフ周波数5Hzという可聴域未満の信号しか通さない超ローパスフィルタだが、
断続的な信号を滑らかに補間することができる。

#N canvas 0 0 454 323 12; #X obj 167 218 dac~; #X obj 168 166 *~; #X obj 110 112 osc~ 440; #X obj 187 111 line~; #X msg 187 71 1 \, 0 1000; #X connect 1 0 0 0; #X connect 1 0 0 1; #X connect 2 0 1 0; #X connect 3 0 1 1; #X connect 4 0 3 0;


メッセージボックス「1, 0 1000」をクリックすると、ピアノのように減衰する音が鳴る。
line~を使って波形を作っている。メッセージは「1から始まり、1000ms(1秒)かけて0に至る」というような意味。
このように音量を制御する波形をエンベロープ(封筒)と呼ぶ。
エンベロープは音色の設計にとって重要である。

周波数を操作する

#N canvas 0 0 450 300 12; #X obj 189 245 dac~; #X obj 190 193 *~; #X obj 209 85 line~; #X obj 133 160 osc~; #X obj 133 124 *~ 440; #X msg 209 45 1 \, 0 1000; #X connect 1 0 0 0; #X connect 1 0 0 1; #X connect 2 0 1 1; #X connect 2 0 4 0; #X connect 3 0 1 0; #X connect 4 0 3 0; #X connect 5 0 2 0;


エンベロープを使って同時に周波数も制御した例。
1から0のエンベロープを440倍しているので、周波数は440Hzから0Hzへと変化する。

#N canvas 0 0 426 476 12; #X obj 175 399 dac~; #X obj 176 347 *~; #X obj 194 83 line~; #X obj 139 306 osc~; #X obj 138 205 *~; #X obj 139 168 *~; #X obj 140 132 *~; #X obj 138 272 +~ 50; #X obj 139 239 *~ 500; #X msg 194 43 1 \, 0 400; #X connect 1 0 0 0; #X connect 1 0 0 1; #X connect 2 0 1 1; #X connect 2 0 6 0; #X connect 2 0 6 1; #X connect 3 0 1 0; #X connect 4 0 8 0; #X connect 5 0 4 0; #X connect 5 0 4 1; #X connect 6 0 5 0; #X connect 6 0 5 1; #X connect 7 0 3 0; #X connect 8 0 7 0; #X connect 9 0 2 0;


エンベロープにいろいろな演算を行なうことで音色を設計している。
なお*~のふたつのインレットに同じ信号を入力しているのは2乗の意味になる。

#N canvas 0 0 450 300 12; #X obj 203 244 dac~; #X obj 204 192 *~; #X obj 223 74 line~; #X obj 35 154 osc~; #X msg 223 35 1 \, 0 400; #X obj 36 123 expr~ pow($v1 \, 8)*500+50; #X connect 1 0 0 0; #X connect 1 0 0 1; #X connect 2 0 1 1; #X connect 2 0 5 0; #X connect 3 0 1 0; #X connect 4 0 2 0; #X connect 5 0 3 0;


しかし同様の演算であればexpr~を用いる方がすっきりする。
expr、expr~、fexpr~はそれぞれ数値、音声信号、サンプル毎の音声信号を演算し、数式で書くことが出来る。
詳しくはオブジェクトのヘルプ、またはhttp://crca.ucsd.edu/~syadegar/expr.html

#N canvas 0 0 448 466 12; #X obj 209 391 dac~; #X obj 210 339 *~; #X obj 228 104 line~; #X obj 75 290 osc~; #X msg 228 65 1 \, 0 400; #X obj 92 165 expr~ pow($v1 \, 8); #X obj 73 212 *~; #X obj 74 252 *~ 800; #X obj 74 119 osc~ 100; #X connect 1 0 0 0; #X connect 1 0 0 1; #X connect 2 0 5 0; #X connect 2 0 1 1; #X connect 3 0 1 0; #X connect 4 0 2 0; #X connect 5 0 6 1; #X connect 6 0 7 0; #X connect 7 0 3 0; #X connect 8 0 6 0; 


osc~の出力を再びosc~に入れている。非常に簡単なFM(周波数変調)の例。
このように、波形に対して様々な演算を行なうことで、様々な音色を作ることができる。
File>Open>doc>3.audio.examplesに膨大な作例が存在する。
pdのドキュメントは非常に充実している。

#N canvas 0 0 448 466 12; #X obj 255 401 dac~; #X obj 256 349 *~; #X obj 274 114 line~; #X obj 119 300 osc~; #X msg 274 75 1 \, 0 400; #X obj 119 217 *~; #X floatatom 119 28 5 0 0 0 - - -; #X obj 119 131 osc~; #X obj 138 167 expr~ pow($v1 \, 8); #X obj 119 262 *~; #X obj 59 215 * 8; #X connect 1 0 0 0; #X connect 1 0 0 1; #X connect 2 0 1 1; #X connect 2 0 8 0; #X connect 3 0 1 0; #X connect 4 0 2 0; #X connect 5 0 9 0; #X connect 6 0 7 0; #X connect 6 0 4 0; #X connect 6 0 10 0; #X connect 7 0 5 0; #X connect 8 0 5 1; #X connect 9 0 3 0; #X connect 10 0 9 1;


ナンバーボックスを操作することで、同じ音色を様々な高さで鳴らす。
100以上の値でゆっくりドラッグしてみる。


(ここでナンバーからメッセージに線がつながっているが、変数がないメッセージへの入力は全てbangとして解釈される。bangについては後述)

関数など

関数とはこのようなものであった。

f = {arg a,b; a+b };

この「f」なる関数は「a+b」をする。引数のところは以下のように書き換え可

f = {|a,b| a+b };

二つは同じ意味である。引数に数をくれるには以下のごとく

f.(2,3);

めでたく「5」と出ます。
ただしこれだけではあまり用途がありませんので、UGenなど入れたのこしらえてみたり

a = {|freq| SinOsc.ar(freq) };
{ a.(200) }.play;

複数組み合わせてみたり

a = {|in,freq,rq| BPF.ar(in,freq,rq) };
b = {|freq| LFNoise0.ar(freq) };
{ a.(b.(1e3),1e3,0.1) }.play;

playの前の文章がとんでもわけわからんことになってますが(余談だが1e3は1000の指数表記)、かように関数で書いて何がうれしいかというと、同じ処理を繰り返すとか別の場所で使いまわす、といったあたりではと思われます。
次にSynthDefの中に入れ込んでみる。以下は2chぶんのディレイタイムを「t」として書いております(t!2は[t,t]の意)。

SynthDef("reverb", {
var
i = Impulse.ar(1);
t = { 0.04.rand+0.01 };
4.do{ i = AllpassN.ar(i,0.05,t!2,1) };
Out.ar(0, i);
}).play;

さらによくわからん例。こうなるともはやSynthDefの中味は謎と言ってもよい感じである。

~res = {|in,freq=4e3| Latch.ar(in, Impulse.ar(freq)) };
~env = { EnvGen.ar(Env.perc(0,0.5), doneAction:2) };
~out = {|in| Out.ar(0, in!2) };

SynthDef("kick", {
var
o = SinOsc.ar(~env**8*250+50,0,~env**2);
o = LPF.ar(LFNoise0.ar(5e3,~env**2),100,1) + ~res.(o);
~out.(o);
}).play;

関数名をグローバルにしてますが、要するに複数のSynthDefで共有しようという。これが必要な場面がどのくらいあるんだか甚だ不明ではありますが、例えばこれみたいに打楽器系の音を何種類か作りたい、などという場合には使えるかもしれません(またも余談だが~resの引数freqには初期値があるので引数を省略可)。
例えばSCでクラスを書いて使おうとすると、まるごとコンパイルし直すとか何気に手間な気がしますが、こうして関数をうまいこと使うことで、よりプログラム言語ぽく(?)なるかもね、というお話でした。

Ruby

次は単なる与太ですが、最近遊びで*1Rubyというのをいじるようになりまして、実際始めてみるまで全然知らなかったが、SuperColliderRubyてなんだか似てますね。それもそのはずで、両方とも共通の祖先にSmalltalkがいる。
まあご先祖が同じとはいえ、似ていると言えば似ている*2、あるいはそうでもない、といった具合なのですが、確かSCの最初の説明でSmalltalkが出てきたと思うのだが、古い言語ゆえか書籍もあまり見当たらないし*3、どんなものかよくわからないままであった。
ゆえにSCはOOPと言われても、うっかりC++やらJavaをイメージしてたらびびるなど笑、いまいちピンと来てなかったりしましたが*4、同じSmalltalkの「強い影響を受けている」Rubyは比較的新しい言語ですし、本もたくさん出てる。
それでSCの説明とか文法や仕様でよくわかんねーなーと思うところとか、もしやRubyをかじってみたりすると多少わかるようになるかも知れないです。と思った。などという、何ら具体例のない与太のまま逃げ切ろうとしているわけですが、これではあまりに無内容なので、一応自分が読んだ書籍など挙げてお茶を濁してみる*5
たのしいRuby 第3版 初めてのRuby

Tokyo SuperCollider #1 - 7/10開催決定

http://tokyosupercollider.blogspot.com/
前半はビギナータイムで全くの未経験でも気軽に参加ください、とのこと。行きたい。

*1:まあ全て遊びなんだけど

*2:「純粋なOOP言語である」とか、イテレータとか

*3:ちなみに言語についてまとまった情報を得るにはぐぐるよか本買ったほうが早い、というのにようやっと気づきました

*4:それ以前に音いじりするのにどれだけOOPを意識する必要があるのか?というのも不明だったり…

*5:まああの、Rubyに関して自分が読んだのは今のところこの二冊だけで、Railsとかおされなものはよくわかりませんし、たぶん今後さわる機会もないのだが

エフェクト(2)

波形の演算

前回はディレイでもっていろいろなエフェクトを作ってみましたが、今度は波形を演算する方法を考える。音データとは波形であり、波形は数字で表現されているのであった。というわけで音量を上げ下げするには単に掛け算すればよい、となります。

SynthDef("amp", {
var in,out;
in = SoundIn.ar(0);
out = in*0.5;
Out.ar(0,out!2); 
}).play;

「*0.5」のかわりに「/2」でも意味は同じ。次にLFOとしてSinOscを掛け算するとトレモロになります。

SynthDef("tremolo", {
var in,out;
in = SoundIn.ar(0);
out = in*SinOsc.ar(5);
Out.ar(0,out!2); 
}).play;

さらにSinOscの周波数を上げていくとリングモジュレーションになる。これというのは二つの周波数を足した周波数と引いた周波数を発生させて音色を変化させる。

SynthDef("ring_mod", {
var in,out;
in = SoundIn.ar(0);
out = in*SinOsc.ar(2000);
Out.ar(0,out!2); 
}).play;

ところで私事ですが、リングモジュレーションなどという単体機でもなかなか売ってないようなマニアなエフェクトが、掛け算だけでできる!というので盛り上がったのが、この道にのめり込むきっかけだった気がします。いわゆる電子音楽の響きよね。
オートパンはPan2の中にSinOscを入れてやる。

SynthDef("auto_pan", {
var in,out;
in = SoundIn.ar(0);
out = Pan2.ar(in,SinOsc.ar(2));
Out.ar(0,out); 
}).play;

音を歪ませるにはどうすればいいだろうか。一番簡単なのは適当な数値で波形をぶった切って潰してしまうことですね。次の例では、MouseXで元の音量を100倍まで変えられますが、clip2でもって波形を±1に制限している。

SynthDef("distortion", {
var in,mod,out;
in = SoundIn.ar(0);
mod = MouseX.kr(1,100,1);
out = clip2(in*mod,1);
Out.ar(0,out!2); 
}).play;

ただ個人的には「波形をべき乗する」という方法が書き方も楽で良いとおもいます。

SynthDef("distortion_2", {
var in,mod,out;
in = SoundIn.ar(0);
mod = MouseX.kr(0.001,1);
out = in**mod;
Out.ar(0,out!2); 
}).play;

もちろんこれもあれこれ作りこまないと、とてもエフェクタ並みの歪みにはなりませんがねえ。
リミッタをかけるにはLimiter、これまたそのまんまである。音量の上限と先読み時間を指定して音量を制限します。ここでは意味なく音量を10倍してから±0.7にリミットしてますが、これだとリミッタというよりコンプレッサですがな。

SynthDef("limiter", {
var in,mod,out;
in = SoundIn.ar(0);
mod = in*10;
out = Limiter.ar(mod,0.7,0.01);
Out.ar(0,out!2); 
}).play;

ノイズゲートにはCompanderが使えます。MouseXでしきい値を指定してそれ以下の音量をミュートしている。

SynthDef("noise_gate", {
var in,th,out;
in = SoundIn.ar(0);
th = MouseX.kr(0.01,1);
out = Compander.ar(in,in,th,10,1,0.01,0.01);
Out.ar(0,out!2); 
}).play;

他にもCompanderでコンプやリミッタが作れる、とあるのですが、どういうことなんだか各自ご研究くだされ(実はよくわかってない)。
いわゆるローファイエフェクトというか下位サンプリングは、Latchを使ったサンプルホールドで作れる。Impulseでトリガされると只今の値をホールドするわけです。

SynthDef("subsampling", {
var in,mod,out;
in = SoundIn.ar(0);
out = Latch.ar(in,Impulse.ar(1000));
Out.ar(0,out!2); 
}).play;

サンプルホールドには他にもいろいろな用途が考えられる。

フィルタ

さてヘルプを見てもらえればわかりますが、scには多種多様のフィルタが用意されております。
よってその全てを説明するのもナニであるので、主たるものを試してみるが、sc for winの新しいバージョンではMouseX、MouseYが使えるようになりましたので、之で戯れてみませう。
マウスの横位置(X軸)でフィルタの周波数を操作します。HPFも試されたし。

SynthDef("lowpass_filter", {
var in,freq,out;
in = SoundIn.ar(0);
freq = MouseX.kr(50,5000,1);
out = LPF.ar(in,freq);	// or HPF
Out.ar(0,out!2);
}).play;

次の例では横位置に加えマウスの縦位置(Y軸)でフィルタの幅を操作します。BRF、RLPF、RHPFなんかもあるでよ。

SynthDef("bandpass_filter", {
var in,freq,rq,out;
in = SoundIn.ar(0);
freq = MouseX.kr(50,5000,1);
rq = MouseY.kr(0.01,2,1);
out = BPF.ar(in,freq,rq);	// or BRF,RLPF,RHPF 
Out.ar(0,out!2);
}).play;

バーブも言わばフィルタの一種でして、全ての音域を通して位相だけを変えるオールパスフィルタという特殊なフィルタの集まりです。AllpassNはオールパスフィルタにディレイを組み合わせたもので、これを4回繰り返している。

SynthDef("mono_reverb", {
var in;
in = SoundIn.ar(0);
4.do{ in = AllpassN.ar(in,0.05,0.05.rand,2) };
Out.ar(0,in!2); 
}).play;

「0.05.rand」でもってdelaytimeを0〜0.05の範囲でランダムにしてますので、音がいろいろな場所にぶつかって散乱している感じになります。また左右別々にdelaytimeを指定すれば擬似ステレオリバーブになる。

SynthDef("pseudo_stereo_reverb", {
var in;
in = SoundIn.ar(0);
4.do{ in = AllpassN.ar(in,0.05,[0.05.rand,0.05.rand],2) };
Out.ar(0,in); 
}).play;

これはAllpassNだけの例なのでアレですが、他のフィルタを組み合わせるなどすれば、もう少しまともなリバーブになると思われます。

FFT

FFT高速フーリエ変換)が何かというのをちゃんと説明できる能力が私にはないのだが、要は入ってきた音を何百とか何千のサイン波に分解するというものです。で、分解されたサイン波を逆FFT(IFFT)すると元の音に戻るんですってよ。すげーなおい。
だがしかーしFFTをIFFTしただけでは単なるCPUの無駄遣いであるので、分解されたサイン波でもって演算することでいろいろなエフェクトが作れます。例えばボコーダと呼ばれるやつ。

SynthDef("vocoder", {
var inA,inB,chainA,chainB,chain,out; 
inA = SoundIn.ar(0);	// 音源A
inB = Mix.fill(4,{|n| LFSaw.ar(1.5**n*200,0,0.1)});	// 音源B
chainA = FFT(LocalBuf(2048),inA);	// 音源AをFFT
chainB = FFT(LocalBuf(2048),inB);	// 音源BをFFT
chain = PV_MagMul(chainA,chainB);	// 音源Aと音源Bの振幅スペクトルを乗算
out = IFFT(chain)/10;	// 逆FFT(なぜか音量下げる)
Out.ar(0,out!2); 
}).play;

IFFTに対する「/10」というのはどこから出てきた数字なんだ、とは思いますけどIFFTから出てくる音は音量が爆発しがちです。よしなにしてくれよとも思うのですが仕様らしい。
さて分解されたサイン波というのは元の音の周波数分布を表しているのですが、音の周波数分布は単にスペクトルとも言う。で、ボコーダは一方のスペクトルでもう一方のスペクトルを削ってやってる感じ、というようなイメージがあります。数学的に正しいかは知らんけど。
FFT関係のヘルプを見るといろいろなやり口があるようですが、なかなか理解が難しい。ここでは簡単にもうひとつだけ、スペクトルのべき乗という例を挙げます。圧縮オーディオってこんな音。

SynthDef("powered_spectrum", {
var in,chain,out; 
in = SoundIn.ar(0);
chain = FFT(LocalBuf(2048),in);
2.do{ chain = PV_MagSquared(chain) };
out = IFFT(chain)/100;
Out.ar(0,out!2); 
}).play;

「/100」というのもやはり適当。

近況、エフェクト(1)

sc3.3 for win

些か旧聞ですがwin版sc3.3の安定版が出ました。
SuperCollider (SourceForge)
SuperCollider 3.3.1 win版について
でもって本文導入部分に多少の変更を要するとは思うのですが、各自よしなにお願い致す。

sc文法一覧

加えてこちらの記事が大変に素晴らしいですので、文法に詳しくなりたい人は是非読みましょう。奥深いわー
SuperCollider基礎文法最速マスター

ディレイいろいろ

さて我々は地道に泥臭く音いじりの方法について考えたいと思う。今回はいわゆるscのエフェクタ的用法について書きますが、その前に、と申しますか、PCならではの遅延(レイテンシ)というものが避けがたく存在しますゆえ、好き勝手なエフェクトは作れるものの単体機並みの反応速度は期待できない、という点に留意します。まあscに限った話でもないと思いますが、
環境にもよるが常にコンマ何秒の遅れが発生しますので、例えば楽器を通す場合には音楽的に「レイテンシが気にならない方向で」の使い方などを追って考える必要があるんでは、と思います。それはともかく、まずは音を入力しなければ話が始まりません。オーディオI/Fの用意は出来ているものとして、入力にはSoundInを使う。

SynthDef("input", {
var in;
in = SoundIn.ar(0);
Out.ar(0,in!2); 
}).play;

単に入力して聴いてるだけですが、ここで例のレイテンシが聴けると思います。これについては今後考えないようにして話を進める。
と言いつつ次はディレイの話なんですが、一番単純なディレイはDelayNであります。

SynthDef("delay", {
var in,out;
in = SoundIn.ar(0);
out = DelayN.ar(in,1,1);
Out.ar(0,out!2); 
}).play;

例によって個々のパラメータについては各自ヘルプを見るように。ここでは1秒遅れで音を返している。任意の時間後に音を遅らせて返せるというわけですが、しかしこれだけでは大して芸がないように思える。
いわゆるディレイと言った時に思い出すような、エコーみたいな効果を作るには、実はCombNを使うのが早い。

SynthDef("comb_delay", {
var in,out;
in = SoundIn.ar(0);
out = CombN.ar(in,0.1,0.1,1);
Out.ar(0,out!2); 
}).play;

ヘルプ見つついろいろパラメータをいじって戴きたいのですが、ここでdelaytimeを極端に短くしてdecaytimeを多めに(とはいえ程々に)とると、パイプの中を通ってきたような妙な音色が出てくる。

SynthDef("comb_filter", {
var in,out;
in = SoundIn.ar(0);
out = CombN.ar(in,0.1,0.01,0.5);
Out.ar(0,out!2); 
}).play;

確か、これのスペクトルを見ると櫛みたいにピークが立ってるのでコムフィルタと言う、と思いましたが、フランジャーやコーラスと呼ばれるものも実はこれの応用。
先程のエコーみたいなやつはフィードバックを使って次のように書くことも可能。

SynthDef("feedback_delay", {
var fb,in,out;
in = SoundIn.ar(0);
fb = LocalIn.ar(1);
out = DelayN.ar(in+fb,0.1,0.1);
LocalOut.ar(out*0.7);
Out.ar(0,out!2); 
}).play;

LocalIn、LocalOutというのが出てきましたが、これで出た音を再入力というかフィードバックしている。何でこんな面倒なことをするかというと、ここではDelayNですが、間にいろいろと他の処理をはさめたりしますので。
さらにLocalOutに「0.7」という数字がありますが、これはいわゆるフィードバック係数でして、どのくらいの割合の音量でフィードバックするかという。これが1以上になるとフィードバック音が消えなくなって音量が爆発します。特にLocalOutの中に書く必要はござりませんぬ。
さて先程のコムフィルタもそうですが、実のところエフェクトと呼ばれるものの多くがディレイから出来ているわけでして、フィルタなんかもそのひとつです。例えばCombNのdelaytimeを超短くすると、なんちゃってローパスフィルタになりますが、

SynthDef("pseudo_low_pass_filter", {
var in,out;
in = SoundIn.ar(0);
out = CombN.ar(in,0.1,0.000001,0.001);
Out.ar(0,out!2); 
}).play;

もちろんこんなものは非常に使いにくいので、scには一通りのフィルタが用意されている。ローパスフィルタはLPF。そのまんまだ。

SynthDef("low_pass_filter", {
var in,out;
in = SoundIn.ar(0);
out = LPF.ar(in,1000);
Out.ar(0,out!2); 
}).play;

DelayNを使って手抜きっぽくステレオ効果を作ることも出来る。

SynthDef("pseudo_stereo", {
var in,out;
in = SoundIn.ar(0);
out = DelayN.ar(in,0.1,[0,0.02]);
Out.ar(0,out); 
}).play;

左右のdelaytimeを微妙に違えているわけですが、定位感が若干アンバランスになるので注意です(先に音が届いた方向に音源があるように感じる)。
さてここまで、最も単純なディレイをDelayNと説明してきましたが(嘘。本当はDelay1と思いますが特殊な用途しか思いつかないので割愛)、DelayはわかるがNとは何だろうか。例えばdelaytimeを一定ではなく変動させてみたりすると妙な事が起こります。

SynthDef("delay_time_mod", {
var in,mod,out;
in = SoundIn.ar(0);
mod = SinOsc.ar(1,0,0.49,0.5);
out = DelayN.ar(in,1,mod);
Out.ar(0,out!2); 
}).play;

実は曲がった音に変なノイズが乗ってるんですが。DelayやCombにはNの他にLやCというのもありまして、曲がった波形の補間をしてくれる、というのだが、要はこうした使い方をするときに変なノイズが乗るのを防いでくれる。まあ好みの問題。

SynthDef("delay_time_mod_2", {
var in,mod,out;
in = SoundIn.ar(0);
mod = SinOsc.ar(1,0,0.49,0.5);
out = DelayL.ar(in,1,mod);
Out.ar(0,out!2); 
}).play;

あとdelaytimeを動かすことでさらにいろいろできます。アナログテープなどの再生速度をいじってるイメージ、と言えばたぶんそんな感じですが、例えばdelaytimeにSinOscをつっ込んでビブラートをかける。

SynthDef("vibrato", {
var in,mod,out;
in = SoundIn.ar(0);
mod = SinOsc.ar(10,0,0.005,0.01);
out = DelayL.ar(in,1,mod);
Out.ar(0,out!2); 
}).play;

ビブラートとそうでないのをぶつけるとフランジャになる。要は変動するコムフィルタ。

SynthDef("flanger", {
var in,mod,out;
in = SoundIn.ar(0);
mod = SinOsc.ar(0.3,0,0.001);
out = DelayL.ar(in,1,[0.001,0.001+mod]);
Out.ar(0,out!2); 
}).play;

ところでこうした時に使うSinOscなどのオシレータをLFOと言いますね。別にLFOは何だっていいわけです。

SynthDef("scratch", {
var in,mod,out;
in = SoundIn.ar(0);
mod = LFNoise0.ar(8,0.5,0.5);
out = DelayL.ar(in,1,mod);
Out.ar(0,out!2); 
}).play;

LFNoise0というのを入れてみましたが、これは言わば乱数発生器です。でもってLFNoiseにも0、1、2がありますが、LFNoise1とLFNoise2は波形の補間をするものであり、この場合の音の違いを各自試してみるが吉。
次にdelaytimeにPhasorを入れて逆再生、なのだがここでPhasorのrateが2なのは何故か、ということを考えはじめるとわけわからなくなれます。ディレイラインの中では常に音が進んでいるわけだから…等々。

SynthDef("reverse", {
var in,mod,out;
in = SoundIn.ar(0);
mod = Phasor.ar(0,2,0,44100);
out = DelayL.ar(in,1,mod/44100);
Out.ar(0,out!2); 
}).play;

ところで上の例では44100という数字が散見されますが、これはPhasorが「1サンプルごとにrate分ずつカウントアップする」という仕組みゆえでありまして、もう少し見やすい書き方をするなら以下のようにもできるかと。

SynthDef("reverse_2", {
var in,mod,out;
in = SoundIn.ar(0);
mod = Phasor.ar(0,2/44100,0,1);
out = DelayL.ar(in,1,mod);
Out.ar(0,out!2); 
}).play;

Phasorのrateをいろいろ変えるとピッチシフトになる。あるいは逆再生はピッチシフトの特殊なやつと言うべきか。

SynthDef("pitch_shift", {
var in,mod,out;
in = SoundIn.ar(0);
mod = Phasor.ar(0,0.5/44100,0,1);
out = DelayL.ar(in,1,mod);
Out.ar(0,out!2); 
}).play;

ピッチシフトにフィードバックをかますとどうなるか。

SynthDef("fb_pitch_shift", {
var fb,in,mod,out;
in = SoundIn.ar(0);
fb = LocalIn.ar(1);
mod = Phasor.ar(0,0.25/44100,0,1);
out = DelayL.ar(in+fb,1,mod);
LocalOut.ar(out/2);
Out.ar(0,out!2); 
}).play;

まあ結構荒っぽいわけですがピッチシフトの原理とは概ねこれであるわけです。これに窓かけしたりオーバーラップさせたりして音をきれいにしたのがPitchShift、という理解。

SynthDef("fb_pitch_shift_2", {
var fb,in,out;
in = SoundIn.ar(0);
fb = LocalIn.ar(1);
out = PitchShift.ar(in+fb,0.1,0.75);
LocalOut.ar(out);
Out.ar(0,out!2); 
}).play;

こんなもんか。結構たくさんになりましたが、ディレイだけでかなりのことができてしまう、というお話でした。

コードを読み込む

小技ですが、SCの起動と同時にコードを読み込む方法。
例えばこのようなコードがあって

SynthDef("hoge", {
o = SinOsc.ar(80,0,SinOsc.ar(1));
Out.ar(0, o!2);
}).send(s);

「mogehoge」というファイルに書いて保存してあるとしますと、startup.scに以下のごとく書き加える。

s.doWhenBooted
{
"(PATH)/mogehoge".load;
"DONE".postln;  
};

s.boot;

SCを起動して

Synth(\hoge);

音が出た。なお

{
arg a, b;

SynthDef("hoge", {
o = SinOsc.ar(a,0,SinOsc.ar(b));
Out.ar(0, o!2);
}).send(s);
}

などとargを使う時は

"(PATH)/mogehoge".load.value(80,1);

とできる。以上。

MIDIコンつなげる

nanoKONTROL買ったぜ!
もはや楽器とは思えぬ値段で笑ける。凄い時代であります。
というわけで早速つなげてみます。まずnanoKONTROL挿した状態でboot、次に

CCResponder({|src,ch,num,val|
[src,ch,num,val].postln
});

としますと

MIDI Sources: 
	MIDIEndPoint("nanoKONTROL SLIDER/KNOB", "nanoKONTROL SLIDER/KNOB")
	.
	.
	.

等々と出ましてつながったようです。超簡単。
あとMIDI受け取るには他にもNoteOnResponderとかとかあるのですが、反応ないし、用は足りるということで。
ノブやスライダを動かすと以下のようにずらずらpostされます。

[ 1, 0, 11, 2 ]
[ 1, 0, 11, 5 ]
[ 1, 0, 11, 7 ]
[ 1, 0, 11, 10 ]
.
.
.

素晴らしい。ちなみにCCナンバーとかはKONTROL Editorの方で設定し直した。番号飛び飛びで出荷時状態だとやりにくーっす。
んだば何だか音出してみる

CCResponder.removeAll;	//一旦かたす

CCResponder({|src,ch,num,val| 
s.sendMsg("/n_set", 1000, "v"++num, val)
});

SynthDef("hoge", {
arg v0,v1,v2,v3,v4,v5,v6,v7,v8;
var
a = Mix(SinOsc.ar(
(1..9)*100,0,
[v0,v1,v2,v3,v4,v5,v6,v7,v8]/127));
Out.ar(0,a/9!2);
}).play;

CCナンバーに"v"くっつけてそのまま引数名にしてみた。ちゃくいぜ。
値は128段階なので127で割ってある。MIDIって粗いんだなー
あとこうした使い方であれば引数にせずともコントロールバスに乗せて

CCResponder.removeAll;

CCResponder({|src,ch,num,val| 
s.sendMsg("/c_set", num, val);
});

SynthDef("hoge", {
var
a = Mix.fill(9, {|n|
SinOsc.ar(n+1*100,0,In.kr(n)/127)
});
Out.ar(0,a/9!2);
}).play;

こうすりゃよかった。
ついでながら再生ボタンと停止ボタンでSynthを出し入れできるようにしてみる。

CCResponder.removeAll;

CCResponder({|src,ch,num,val|
case(
{num==45 && val==0},{s.sendMsg("/s_new", "hoge", 1000); "play".postln},
{num==46 && val==0},{s.sendMsg("/n_free", 1000); "stop".postln},
{num<9},{s.sendMsg("/c_set", num, val)}
)
});

SynthDef("hoge", {
var
a = Mix.fill(9, {|n|
SinOsc.ar(n+1*100,0,In.kr(n)/127)
});
Out.ar(0,a/9!2);
}).send(s);

などと手元で一括できますとイカしたライブなんかもできそうです。

メモリサイズ

時間が足りない

コードが長大になってくると、時々ディレイを使いすぎるということが起こります。
個々のディレイが短くても、合計時間には上限があるらしい。何十秒もあれば充分だろうと思うかもしれないが、そんなことはない。って俺だけか
これは23秒ディレイ。

SynthDef(\hoge, {
arg dur;
var
a = DelayN.ar(SinOsc.ar(100),dur,dur);
Out.ar(0,a!2);
}).store;

Synth(\hoge, [\dur, 23]);

これがもし24秒ディレイだと、

Synth(\hoge, [\dur, 24]);

alloc failedなんたらと言われ、メモリ増やせと死ぬほど怒られます。うぜーのでAlt+.で黙らす。
そんなら只今のメモリサイズとはいかほどであるのか?

s.options.memSize

特に設定をいじってなければ8192と出ます。
ちなみに8192とは2の13乗(2**13)。なほ、メモリサイズは2**nとするがいいらしい。根拠は知らぬ。
ある方によれば、使えるディレイ時間とはメモリサイズをサンプリングレート(通常44100)で割ったものだそうです。
しかし8192/44100とは1(秒)にも満たないのでこれはおかしい。
さっきは23秒までできたのであった。では23秒にサンプリングレートをかけたものは2**ナニであるのか。

log2(23*44100)

19.952052991389

log2(24*44100)

20.013453536053

メモリサイズをサンプリングレートで割る説に従えば、恐らくメモリサイズは2**20であることになりますが、プラス7乗ということは単位が違うとしか思えない。それはともかく、

2**20/44100

23.777233560091

実際やってみると、確かにだいたいdur=23.77まではいけるもよう。dur=23.777だとコケますが。
ではメモリサイズ増やす。startup.scを開いて、

s.options.memSize = 2**19;
s.boot;

などと書き足しましてAlt+K。
このmemSizeは数字が大きすぎると起動すらしなくなるのであり、当方の環境では偶々2**19が上限だったということです。
s.bootはおまけですが、便利ですね。何で今まで気付かなかったんだろう。さて、

Synth(\hoge, [\dur, 24]);

無事走ったもよう。

2**(19+7)/44100

1521.7429478458

さきほどのプラス7乗説によれば、上のごとく使えるディレイタイムは約25分。
この段階で既にお腹一杯ですが、何でも余裕があることは良いことですね。

Synth(\hoge, [\dur, 1521]);

おー走った。
1秒増やすとまたキレられたので、どうやらプラス7乗説はありなもよう。
それにしても本当に25分ディレイできてるのかというと、結論は25分後であり、ヒポクラテス先生の言うとおり、あっという間に死ぬ我々ですから、ここはNRT(レンダリング)にて検証します。
どんどんWAVに書き出して、プレイヤーでシークしたりすればいいじゃんていう。

NRTも時間が足りない

x = [
[0, [\s_new, \hoge, 1000, 0, 0, \dur, 1521]],
[1800, [\c_set, 0, 0]] 
];

o = ServerOptions.new.numOutputBusChannels = 1;
Score.recordNRT(x, "hoge.osc", "hoge.wav", options: o); 

あれ、またメモリ足りねーとか。
んだてめえやんのかこら。
などと凄んでる場合ではなく、果たしてdurいくつなら通るのかというと、悪い予感どおりと申しますか、またもや23.77のあたり。
デフォルトと変わってねー!ということはNRTではまた別のメモリの使い方をしておるのだろう、とか、何が何だか私にはよくわかりませんが、startup.scのmemSizeが効いてないのは確かなようです。
他にもうまい策がありそうだが、とりあえず考えたのは、BufDelay使う。

SynthDef(\hoge, {
arg dur;
var
a = BufDelayN.ar(10,SinOsc.ar(100),dur);
Out.ar(0,a!2);
}).store;

つまりScoreにb_allocとでも書けば、NRTでもメモリを使えるだろうという読み。

d = 1800;
x = [
[0, [\b_alloc, 10, d*44100]],
[0, [\s_new, \hoge, 1000, 0, 0, \dur, d]],
[2000, [\c_set, 0, 0]] 
];

o = ServerOptions.new.numOutputBusChannels = 1;
Score.recordNRT(x, "hoge.osc", "hoge.wav", options: o); 

でけた。
しかもお気づきかもしれませんが、数字おかしいです。これは30分ディレイ。
プレイヤーでシークしてみると、確かにそのようです。

まとめ

以上、当方の環境では書き出せましたが、何しろメモリ云々のことですし、他でもできるかは知りません。
あと、何分まで出来るか、とか追究するのも無意味な領域に達している気がするので、やりません。30分あればさすがにどうにかなるだろう。というかしろ。
あと、他のUGenとの絡みもわかりませんし、サンプリングレートやらチャンネル数が変われば、使える時間も自ずと変わると思われます。