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:」と、目的と条件を以下のように明確にしている。
もっともな話である。(^_^;)
- The stupid computer will only accept syntactically correct statements in this language.
- You need to become aware of standard mechanisms of computer languages, like iteration, conditional execution, messaging and containers.
- It's frustrating if you have a specific musical task in mind to have to deal with this computer language stuff.
次の「payoff」を飛ばして、既に行っている必要条件が以下のように示されている。3番目の情報は収穫である。
- 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.
- Before doing any sound synthesis, turn on the localhost server, by pressing 'Boot' on the grey localhost server graphic.
- 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で、という事だが、何故か今回も起動しなかった。 まぁ、起動しないものは無視して進めていくしかない(^_^;)。
楽音合成の冒頭は「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.」ということで次に進もう。Server.default=s=Server.internal; s.boot; 電子楽器の音源の歴史と同様に、次の楽音合成は「減算方式(Subtractive Synthesis)」である。 元信号として広域成分を含んだ波形を使って、ここにLPF系のフィルタをかまして音色などを形成するというもので、 ミニムーグなどアナログシンセもここから始まった。 詳しくは、ちょうど、静岡県工業技術センターの人が絶版の著書をPDF化してくれたので、 コンピュータサウンドの世界 を参照されたい。
scopeは聞かないのでサンプルをちょっと改訂してみると、最初の例は以下のホワイトノイズである。 こんな音である。
{WhiteNoise.ar(0.1)}.play
このホワイトノイズにLPFをかける、というサンプルが以下である。 確かにカットオフ1000Hzで高域が減衰して音が丸くなった。 こんな音である。
{WhiteNoise.ar(0.1)}.play
このLPFは以下のような書式でUGenなど色々な入力に対するフィルタを実現する。
上の例で1000Hzだったカットオフ周波数を時間的に動かすことで、タイムバリアントな面白い音色が出来ることになる。 ここには、Ma/MSPにもあった「line」「line~」と同じように「Line.kr」というのがあり、以下のような書式である。LPF.ar(input signal, cutoff frequency, ... ) そこで、上の例のホワイトノイズにLPFをかけるサンプルのカットオフ周波数を10000Hzから1000Hzまで10秒間かけてリニアに下げる、というのは以下のようになる。 こんな音である。Line.kr(10000,1000,10) // take ten seconds to go from 10000 to 1000 {WhiteNoise.ar(0.1)}.play
この後には、Subtractive Synthesisのソースとし使う色々なオシレータとノイズ源とフィルタが以下のように紹介されている。
前回やったように、キーボードショートカットでソースコードを引くのは「Cmd+j」である。 せっかくなので、ここで登場したものはソースを以下のように出してみた。 ピンクノイズとかHPFは中身が無くて、詐欺みたいなものだった(^_^;)。
- Oscillators
- [Saw]
- [Blip]
- Noise Sources
- [PinkNoise]
- [LFNoise0]
- some example filters:
- [HPF]
- [BPF]
- [Resonz]
具体例として、以下はLFノイズにレゾナンスフィルタをかけた例である。 いいカンジの、 こんな音がしてきた。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) } } {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である。 こんな音である。 文句なく、もっとも汚く濁っているのは、平均律である。 こんな音程の音楽を幼い頃から被爆して育つ子供が気の毒である。上の例は、簡略形として以下のようにも書ける。「[」と「]」の登場である。
ここまでモノラルの片チャンネルだったが、次に登場するのは、「pan2」である。 これはモノラルのサウンドを入力として、ステレオの音場に展開する。 定義は以下のようになっている。{SinOsc.ar([400,660],0,0.1)}..play 以下の例では、ホワイトノイズをマウスのX座標によってパンニングしている。 こんな音である。Pan2.ar(input signal, pan position) pan position goes from -1 (hard left) to 1 (hard right) {SinOsc.ar(400,0,0.1) + SinOsc.ar(660,0,0.1)}.play
SuperColliderでは、マルチチャンネルのサウンドには配列を使う。配列も「[」と「]」で括る。 以下の例では、3チャンネルの音としてSuperColliderが生成しているが、たまたまコンピュータが2チャンネルステレオとなっているので、前述の2音と同じように聞こえる。
「Pan2」がモノラル入力の信号をステレオ信号に振り分けたのに対して、ステレオ以上のマルチチャンネルのサウンドをモノラルに合成するのが「Mix」である。 以下の例では、2チャンネルの音がミックスされてモノラルになる。{SinOsc.ar([400,660,870],0,0.1)}.play 以下の例では、2音をステレオとして出したものをモノラルにミックスした後でマウスのX座標によってパンニングしている。 こんな音である。{Mix(SinOsc.ar([400,660],0,0.1))}.play {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ペアは、それぞれ同等である。
もっとも基本的な「SinOsc UGen」のシンタックスと推奨入力値は以下である。{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 位相とオフセットのdefaultはゼロなので、以下の2つの例は同等である。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 以下の4つの例も全て同等なので混乱しないで・・・とのことである(^_^;)。 音量については「mul: -20.dbamp」ということでデシベル表記もOKである。{SinOsc.ar(MouseX.kr(440,880), 0.0, 0.1, 0.0)}.play {SinOsc.ar(MouseX.kr(440,880), mul:0.1)}.play 「変調」はまた後で取り上げるが、ここでの乗算と加算を、以下の例のような場合に使える、と補記されていた。 こんな音がしてきた。{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」の定義をした上で発音する。 ここで、a = {SinOsc.ar(440)*0.1}.play のいずれかを実行すると、その「a」のサウンドだけ止まる。 改めて、メモリに残っている「a」を起動するのは、以下の3つのうちのいずれかでよい。a.run(false) a.run(0) 次のトピックは「Arguments」である。a.run(true) a.run(1) a.run を実行すると440Hzのサインが鳴るが、ここで引き続きa = {arg freq=440; SinOsc.ar(freq)*0.1}.play を実行すると、330Hzに変更できる。これは定義の段階で「arg freq=440;」としていたからである。 以下の例は「これをやってもCRASHするよ(^_^;)」という、駄目な例である。UGenをアーギュメントにしては駄目である。a.set(\freq,330) アーギュメントは2つ同時に変更することも出来る。 以下の例では、最初に起動したサウンドから、次を何度か実行すると こんな音がしてきた。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 = 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 2 の 3.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()」という関数は非常に巨大で強力である。 ヘルプを引くと、その中身は以下であった。
また、エンベロープを生成するUGenの「EnvGen()」という関数のヘルプを引くと、その中身は以下であった。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) } } 一例として、以下の場合には、最初の配列で3点のY座標が与えられているので、初期値は1.0でここから0.0に向かい、最後はまた1.0に至る。2つ目の配列がX方向の値であり、ここでは1.0と0.5とに、つまり2:1に分割されている。X方向の単位は秒(sec)である。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 } } 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
もう少し複雑にするために、上記の例を
とすれば、
- frequency of Saw over 0.5 second
- amplitude go to zero over 2 seconds
となるが、これではカッコのネスティングでわけが判らない(^_^;)。 そこで、まったく同等だが、以下のように記述すると理解しやすい。 こんな音がした。{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 2 の 3.2 Modulation synthesis.html からである。 いよいよ「変調合成(modulation synthesis)」である。この章では、internal serverを起動した上で、スペクトルアナライザとして「Lance Putnam's Frequency Scope」をバックグラウンドで起動しておく、とのことである。 しかし何故かinternal serverをーは起動しないので、localhost serverを起動して「FreqScope.new」を実行してみた。 以下のようになった。
この「FreqScope」というのをヘルプで引くと、以下のようなものらしい。まだ詳細は不明である。(^_^;)
しかしこれだけで何も起きないので諦めて(^_^;)、中身に進んだ。 「modulation synthesis」では2種類の信号があり、一つはcarrierである。これをもう一つのmodulatorで変調する。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 } }
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つのスペクトルを持つ。 以下の三角関数の「積和の公式」から明らかである。
次のサンプルは「振幅変調 Amplitude Modulation (AM)」である。これも「carrier * modulator」ということで、2つの信号を単純に乗算するだけである。ただしリング変調と違って、モジュレータが非負、つまりユニポーラとなっている。 ユニポーラとバイポーラは以下のように指定できる。cos(C)*cos(M) = 0.5*(cos(C-M) + cos(C+M)) 以下の例では、マウスカーソルのX座標とY座標のそれぞれに対応させて指数特性で変化する周波数のサインを生成し、AM変調させている。 こんな音がした。{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) ( { 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」のスペクトルも加わるからである。
そして教科書通りに、次のサンプルは「周波数変調 Frequency Modulation (FM)」である。 サウンド生成のFM音源方式は、1967年にJohn Chowning博士が発明し1973年に刊行、そして1983年にYAMAHAが特許を買ってシンセサイザ「DX7」を発売し、これは世界中で30万台も売れた。cos(C)*(1+cos(M)) = cos(C)+ 0.5*(cos(C-M) + cos(C+M)) ここまでに既に出て来た「ビブラート」というのも、実はFMである。ただしモジュレータの周波数が非常に低かった。 FM音源ではモジュレータの周波数もキャリヤと同等に高くした、というのが画期的なアイデアである。 実は上述のRMでもAMでも「モジュレーションの深さ」というパラメータが存在するが、FMではその効果が大きいので、一般にFM方式には以下の3つのパラメータがある。
2つのパラメータであればマウスのXとYとで実験できるが、3つとなると無理なので、以下の例では、新しいウインドウに3つのスライダーを表示させて、これで上の3つのパラメータをそれぞれ変更できるようになっていて、新しいGUIのサンプルでもある。 あれこれいじると、 こんな音がした。
- carrier frequency C
- modulation frequency M
- modulation depth or frequency deviation D
( 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が登場して劇的にウケたのは、その多彩・豊富なスペクトル成分にある。 デプスを軽くしても、以下のような成分が出て来るので、デプスを深くすれば、さらに凄いことになる。
ヤマハのFM音源では、音楽的により有効になるように、パラメータとしてデプスでなく、インデックス「I= D/M」を使っている。 以下の例では、デプスでなくインデックスを変更してサウンドを試せる。 こんな音がした。C + kM where k is any integer, ie. C. C+M, C-M, C+2M, C-2M, ... ( 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には
という4つのパラメータがあるので、このphaseの項を変調してやれば、何か出て来るだろう、という事である。 まぁ、激しく変化させればほとんどFMと違わないのでは・・・と予想できる。 以下の例では、3つのスライダーで「carrfreqslider」「modfreqslider」「modindexslider」の3つを動かしている。 ぐりぐりすると、案の定、 こんな音がした。SinOsc.ar(freq, phase, mul, add) ( 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」は以下のように定義されている。
他に参照するものとして、以下は「SinOscFB」のヘルプである。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) } } 他に参照するものとして、以下は「Vibrato」のヘルプである。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) } } これで「変調」はおしまいである。 だいぶいい音がしてきた。 次は Workshop materials for Computer Music (G6002) の 3. Sound Synthesis 2 の 3.3 Moresynthesisexamples.html である。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) } } この章の最初のトピックは「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」のヘルプは以下である。
「Buffer」のヘルプはとんでもなく長いので別ファイルにした。 これである。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 } } サウンドファイルは これ(voice.aiff)であるが、これをSuperColliderのフォルダの下の「sounds」というフォルダの中に入れておく。 そしてまず最初に、以下を実行する。
そして、以下の例ではマウスのXとYで、このサンプリングデータを読み出すレートと変調を制御している。 こんな音がした。//run me first to load the soundfiles ( b=Buffer.read(s,"sounds/voice.aiff"); ) //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 1 の 4.1 Programming.html からである。サクサクと行きたいものだ。どうやらここは「Syntax for SuperCollider as a Programming Language」ということらしい。 他の処理系と比較するにしても、サクサク行ってしまおう。 最初は以下のコメントである。ノーコメントである。
次は、以下の「Brackets」である。これも既に出て来ているのでノーコメントである。 ネスティングは自在である。Comments //this is a comment /* this is also a comment */ 変数と値の割り当てのサンプルである。これはlocalhost serverを起動しなくても以下のように結果が出た。( //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] ( 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でも基本的には、以下のように上から順に実行する。
ここに「if」と確率1/2の「0.5.coin」を組み合わせると、以下のように実行したたびにランダムな結果となる。( "me first!".postln; "now me!".postln; "don't forget me!".postln; ) me first! now me! don't forget me! don't forget me! 「if」の定義は以下である。( "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 (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! 次のトピックは「Classes」である。クラスはカプセル化のために重要なツールであり、既に「SinOsc」、「SCWindow」、「MouseX」・・・など、たくさん出ているものである。 他のオブジェクト指向プログラミング言語と同様に、クラスの記述のしかたについて、SuperColliderは以下のようなルールがある。( var a,b; a=5; b= if(a==6, 11, 7); b.postln; ) 7 まず、大文字で始まるのは「class name」である。 ここに以下のようにドットでメソッドを繋げることができる。
postウインドウでこのメソッドの部分をハイライトして「command + y」で、このメソッドの解説がズラズラと出て来る。 「ar」の解説はとんでもなく長いので別ファイルにした。 これである。LFSaw.ar //ar is the method 「.dumpClassSubtree」というメソッドをひっつけて実行すると、その上のクラス(superclass)の解説を表示できる。 ここでは
というのを実行させたが、そのpostウインドウの中身はとんでもなく長いので別ファイルにコピペした。 これである。UGen.dumpClassSubtree すべてがObjectのクラスに由来しているので、「Object.dumpClassSubtree」とすることで、全体の階層構造を見ることが出来る。 新しいクラスを追加したい場合には、それを「.sc」という拡張子のファイルにして、「SCClassLibrary folder」に置いた上で、「command + k」によってリコンパイルすればよい。
これで「4.1 Programming.html」は終わったので、次は 4. SC Programming 1 の 4.2 Arrays.html である。配列「Arrays」である。 SuperColliderは多数のクラスの集合であるが、簡単な例では従来の言語と同様に、以下のように直接的に「[」「]」ブラケットで囲んだデータの配列を定義できる。
もう少し複雑な場合には、以下のように関数を使って配列を生成することも出来る。a= [5,6,7,8,9,10]; 配列の生成にはいくつかのトリッキーな(有用な)方法がある。 「Array.series」は順番に並ぶ要素の配列である。 以下の2つの例は実行してみると判るように同等である。a= Array.fill(6, {arg i; i+5}); 以下の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 ] 上の例はいわば「等差数列」を要素とする配列を生成するが、「Array.geom」は等比数列を要素として生成する。 以下のように、一気に増大するので注意が必要である。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.rand」はランダム要素を指定した数だけ並べた配列を生成する。以下のように乱数の値域を指定できる。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(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 ] 配列の個々の要素をインデックスを使って指定する方法は以下のようなものがある。 最後には「crashするからやめれ(^_^;)」というのもあるが実行してみた。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 ] Javaと同様に、以下のようにも記述できる。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 = ... 「[1,2,3] 」のように表記する通常の配列(要素の書き換えが出来る)に対して、「#[1,2,3]」のように表記した配列がある。 これはSuperColliderの効率化のために、定数データを格納した配列で、要素は参照するだけ、というものである。 両方を比較した以下の実例で様子が判る。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 ] もう少し複雑な配列としては、UGenが要素になる配列、というのもある。 以下の例では4つのUGenを定義して鳴らしいてる。 しかしパソコンがステレオなので最初の2チャンネルだけが鳴り、 こんな音がした。[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 = ... ( //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 1 の 4.3 SynthDefs.html である。SuperColliderの中核、「SynthDefs (Synthesizer Definition)」である。
これまで多くの例で「{}.play」としてサウンドを鳴らしてきたが、granularとかensembleとかの複雑なサウンドを鳴らすには、多数のUnit Generatorsを組み合わせるためにSynthDefsを使うのが本命である。 今回のSuperColliderでは、OSCとかGainerとかArduinoとかで外部から制御するサウンドサーバとしたいので、いちいちSuperColliderのpostウインドウで選択してenterする、というのでなく稼働させたいので、これは重要である。
まずは実例である。たった1行、まず以下を実行する。
これで、「\sine」という名前のシンセが定義される。 あとは、SynthDef(\sine, {Out.ar(0,SinOsc.ar(Rand(440,880),0,0.1))}).add; とすると、その音が鳴る。どんどん起動していくと、以下のようになり、遂には音が割れてきて、 こんな音がした。Synth(\sine);
上の例が、これまでの「{SinOsc.ar(440,0,0.1)}.play」というのとどう違うか、は以下である。 たったこれだけで、起動するたびにどんどん面白い音になっていく。
また、上の例では単に「Synth(\sine); 」としたので、重なった音のどれかを後で消すことは出来ないが、以下のようにすれば個別に消してメモリを解放できる。
- the SynthDef wrapper SynthDef(\nameofsynthdef, { ...}).add
- use of the Out UGen
- Rand(440,880) instead of a fixed constant frequency
SynthDefの名前の書式には「\aaa」の他に、以下のように「"aaa"」も使える。 バックスラッシュは出ないこともあるので、こっちの方が良さそうである。a=Synth(\sine); b=Synth(\sine); c=Synth(\sine); a.free; b.free; c.free; SynthDefの出力はdefault Serverに送られる。サーバが起動されていないと音は出ないが静かに無視される(^_^;)。 これはlocalhost serverの緑色の「-> default」でも確認できるが、postウインドウでも以下のように確認できる。SynthDef("aaa",{Out.ar(0,SinOsc.ar(440,0,0.1))}).add Synth("aaa"); //now this この後に「warning」として古いバージョンのSuperColliderに関する記述があったが、ここは軽くスルーした(^_^;)。 「.add」については、以下のように「.writeDefFile」と「.store」との違いを覚えておきたい。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 SynthDefは、それ自体の名前を呼び出して発音するだけでなく、内部で定義されているパラメータを指定して発音させることが出来る。以下の例では、まずdefaultで発音し、さらに周波数を変えて発音している。 こんな音がした。
- .add - just sends the SynthDef to the synthesis server at this moment, doesn't place any file on disk.
- .writeDefFile - just writes the SynthDef into a file on disk, doesn't load it to the synthesis server.
- .store - writes the file on disk, so it's loaded every time you start the synthesis server from now on, and also sends it immediately so it's available right away.
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が作れる、というものである。
これで「4.3 SynthDefs.html」は終わったので、次は 4. SC Programming 1 の 4.4 Exercises.html である。 ここには4つの「問題」が示されているが、セミナーでは「来週に回答を提示して議論しよう」とあった。 その「来週」は次のセクションなので、ここでオシマイとした(^_^;)。 ここまでを「日記(2)」として区切りにして、次は「日記(3)」として 5. Interaction から始めてみたいと思う。( 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
SuperCollider日記(3)
「日記」シリーズ の記録