axjack's blog

axjack is said to be an abbreviation for An eXistent JApanese Cool Klutz.

releaseの方法 in depth

では、OSCコマンド(Server Command)でreleaseさせるにはどうすればよいのだろう?

前回書いたのだが、もう少しまとめる。というよりもっと前にも書いていた。今回含め3回も書くぐらいだし、releaseは重要なのだ。

releaseするには、大きく分けると、

  • setメソッド
  • releaseメソッド
  • sendMsgメソッド

の3つ方法がある。このうち、set/releaseメソッドはnodeインスタンスの、sendMsgメソッドはserverインスタンスのメソッドである。どれをいつ使うかは状況次第。

コード

//SynthDef
(
SynthDef(\h,{
    |amp=0.3,gate=1,rT=1,freq=440|
    var sig,e,eg;
    sig = SinOsc.ar(freq) + SinOsc.ar(freq + 1);
    e   = Env.adsr(releaseTime:rT);
    eg  = EnvGen.kr(e,gate,doneAction:2);
    sig = sig * eg;
    
    Out.ar(0,Pan2.ar(sig,0,amp));
}).add;

)

/* how to release */
//set
~x = Synth(\h,[\amp,0.1,\rT,0]);
~x.set(\rT,5);
~x.set(\gate,0);

//set gate: negative value
~x = Synth(\h,[\amp,0.1,\rT,0]);
~x.set(\gate,-5-1);

//release
~x = Synth(\h,[\amp,0.1,\rT,0]);
~x.release(5);

//sendMsg release -> gate: 0
~x = Synth(\h,[\amp,0.1,\rT,0]);
s.sendMsg('/n_set',~x.nodeID,\rT,5);
s.sendMsg('/n_set',~x.nodeID,\gate,0);

//sendMsg gate: negative value
~x = Synth(\h,[\amp,0.1,\rT,0]);
s.sendMsg('/n_set',~x.nodeID,\gate,-5-1);

メモ

/* how to release */以降は全て5秒でreleaseする方法である。

saw stringsを作って余韻(release)の伸びを確かめる

saw strings

その名の通りSaw.arを使えばできるだろうと思いググると、

まずオシレーターには通常のこぎり波や細いパルス波など倍音成分が多い波形を用います。最初に述べた通りこれらのオシレーター波形はストリングス系の楽器に近い倍音分布を持っています。これをデチューンするかオクターブ違いにするのがストリングス音の基本的なオシレーター設定です。

チュートリアル:シンセサイザー入門

と書いてある。なので、2つのSaw.arを素直にデチューンして作ってみることにした。

releaseの伸び

ストリングスと言えばreleaseの伸びが印象的。releaseの値を0から0.05ずつ加算して、混ざり合う音がどんな心地になるのかを確かめる。

コード

(
SynthDef(\saw_strings,{
    |freq=440,amp=0.3,gate=1,detune=2,rate=1,rT=0|
    var sig,e,eg;

    sig = Saw.ar(freq) + Saw.ar(freq * rate + detune);
    e   = Env.adsr(0.5,5,0.9,rT,curve:\sin);
    eg  = EnvGen.ar(e,gate,doneAction:2);
    sig = RLPF.ar(sig,freq*2,0.1);
    sig = sig * eg;

    Out.ar(0,Pan2.ar(sig,0,amp*SinOsc.kr(6.5,0,0.1,0.8)));
}).add;

)


(
~r = r({
    var rt = 0;
    var f = [1,1/2,3/2,4/3,5/4];
    var freq = 440!f.size * f;
    inf.do({
        "releaseTime: %\n".postf(rt);
        ~x = Synth(\saw_strings,[\freq,freq.choose,\detune,1,\rT,rt]);
        1.wait;
        ~x.release;
        rt = rt + 0.05;
        1.wait;
    });
});

)

~r.reset;
~r.play(TempoClock( 144/60 ));
~r.stop;


/*
http://danielnouri.org/docs/SuperColliderHelp/ServerArchitecture/SynthDef.html

    // if value is negative, it overrides the release time, to -1 - gate
        x = Synth(\synthDefTest);
        x.set(\gate, -5); // 4 second release
*/

~x = Synth(\saw_strings,[\freq,440,\detune,1,\rT,0]);
s.sendMsg('/n_set',~x.nodeID,\gate,-5);

メモ

感想

  • Synth面
    • デチューンしただけの割には、シンセサイザのプリセットにあるようなsaw stringsの音に聞こえる。
  • Routine面
    • releaseTimeが1を超えた辺りから音のオーバーラップが始まる。音楽的作品には全く以てほど遠いものの、時間が経つにつれてコーラル(合唱)のような響きを感じた。

releaseの方法

Synth(~xとする。)にEnvGenを噛ましてgateをaugとして持っている場合、~x.releaseを実行すればreleaseされる。また~x.release(3)と実行すれば3秒後にreleaseされる(release値の上書き)。

では、OSCコマンド(Server Command)でreleaseさせるにはどうすればよいのだろう?*1それは、

s.sendMsg('/n_set',~x.nodeID,\gate,-5);

のように、sendMsgコマンドの引数に\gate, <負の値>をセットすれば良い。そうすると、-1 - <負の値>後にreleaseされる。

動画に撮ってみた

画面キャプチャではなくiPhoneで撮ったものをiPhoneからYouTubeにアップロードしてみた。こちらより。録音や録画はコードを再実行する手間も省けて便利。

参考

チュートリアル:シンセサイザー入門

Server-Command-Reference

SynthDef

*1:これを書くきっかけ:s.sendMsg('/n_release',node_id)やs.sendMsg('/n_set',node_id,\release)ではreleaseできなかった故。

do-loopのネスト

yoppaさんのなかなか良いサンプルができたのSynthDefがとてもきれいで感動した。RLPFのcutoff frequencyをLFNoiseで動かしているのだけど、右と左でcutoffの周期が違うので立体感が増すように感じる。こんな風に作れたらなぁ。。

今回作ったもの

  • SynthDef

    • yoppaさんがSawを3つステレオで重ねていたのをパkインスパイアして、4つほど重ねてみる。
    • freq * 2 => 1オクターブ
    • freq * 4.5 = freq * 3 * 3/2 = freq * (3/2) * (3/2) * 2 => (完全五度上の完全五度 = 長9度?)の1オクターブ
    • freq * 6 = freq * 2 * 2 * (3/2) => 完全五度上の2オクターブ
  • Routine

    • i = 0の時, 2**(i+1) = 2, (2**i).reciprocal = 1 => 四分音符を2拍
    • i = 1の時, 2**(i+1) = 4, (2**i).reciprocal = 1/2 => 八分音符を4拍
    • i = 2の時, 2**(i+1) = 8, (2**i).reciprocal = 1/4 => 十六分音符を8拍

コード

(
SynthDef(\h,
{
        arg freq = 48;
        Out.ar(0,(
            LFTri.ar([freq,freq-5]) + 
            LFTri.ar([freq * 2 + 1,freq * 2 - 1]) +
            LFTri.ar([freq * 4.5 + 1,freq * 4.5 + 2]) +
            LFTri.ar([freq * 6 - 2,freq * 6 + 5])
        ) * 0.1 * EnvGen.ar(Env.perc,doneAction:2)
        );
}).add;

)


(
~r = r({

    inf.do({
        3.do({|i|
            ( 2**(i+1) ).do({
                Synth(\h,[\freq,48*(i+1)]);
                (2**i).reciprocal.wait;
            });
        });
    });

});

)


~r.reset;
~r.play(TempoClock(144/60));

Envのblendと録音

難しいやり方はググった方が早い。ひとまずざっくりとblendと録音をしてみる。

blend

blend (argAnotherEnv, argBlendFrac: 0.5)ブレンドされたEnvが返る。実際に使うときはEnvGenenvelopeに入れる。

~e         = Env.perc(2,level:0.5);
~e_delay2  = ~e.delay(2);
~e_blend   = blend(~e,~e_delay2,0.9);

[~e,~e_delay2,~e_blend].plot(maxval:0.75);

plotで確認すると以下のようになる。上から順に~e~e_delay2~e_blend。センスよく混ぜれば素敵なエンベロープが得られる、のではないかと思う。

f:id:axjack:20171117233536p:plain

録音

最低限やるべきなのは以下の3つで良い。

  • s.prepareForRecord;
  • s.record;
  • s.stopRecording;

.prepareForRecordでバッファなどを確保し、.recordで録音開始。そして.stopRecordingで録音終了。.prepareForRecordをやっておかないと

slight delay before recording starts for real

となるらしい。録音前にしれっと実行しておこう。

録音が終わるとどこに保存されるかというと、Post Windowに表示されるRecording: /...Recording Stopped: /...である。Macだと/Users/<username>/Music/SuperCollider Recordingsに保存されるのだと思われる。

f:id:axjack:20171117235603p:plain

難しいやり方はググった方が早い。ひとまずざっくりの方針なので、これで一応録音ができる。

作ったコード

(
SynthDef(\h,{
    |amp=0.1,freq=440,gate=1,aT=2|
    var sig,e,eg,y;

    y   = SinOsc;
    sig = Mix.ar(y.ar([freq,freq*(4/3),freq*(5/4)]));
    e   = Env.perc(aT,level:0.5);
    e   = blend(e,e.delay(2),0.5);
    eg  = EnvGen.ar(e,gate,doneAction:2);
    sig = sig * eg;
    sig = FreeVerb.ar(sig,0.3,0.9,0.7);

    Out.ar(0,Pan2.ar(sig,0,amp));
}).add;
)

//~x = Synth(\h,[\amp,0.5,\freq,400,\aT,5]);

(
~r = r({
    var f  = [400,400*1.2,400/1.2,400*1.5,400*1.9,400];
    //var w  = Array.rand(f.size,5,15);
    var w  = [11,5,8,6,15,8];
    var wi;

    f.do({|item,i|
        wi = w[i].postln;
        Synth(\h,[\amp,[0.3,0.15,0.4].choose,\freq,item,\aT,wi]);
        (wi-2).wait;
    })

});
)


s.prepareForRecord;
~r.reset;

(
s.record;
~r.play;
)

s.stopRecording;

解説や補足

  • freq*(4/3)はfreqに対して純正完全4度、freq*(5/4)はfreqに対して純正長3度である。
    • 例えば、freq = 60.midicps = ドなら上記はドミファを表す。
  • 録音したものはsoundcloudにアップロードしてみた。

参考

バブルソートの音を聞く

var note = [63,61,69,64]; note.sort;と書けばnoteはソートされるのであるが、ソート中のnoteは見ることも聞くこともできない。聞く価値があるのか・ソート中の配列に芸術的な価値があるのか、は神棚に上げておくとし、ひとまずバブルソートしているときの音を聞いてみたい。

コードを実行すると音がどんどん昇順になることが分かる。ただ、あまり綺麗ではないな...というのが率直な感想である。

コード

/*
    Bubble Sort
*/
(
~r = r({
    //var note = [ 68, 71, 70, 61, 60, 64, 67, 65, 63, 66, 69, 62 ];
    //var note = (60 .. 71).scramble;
    var note = Array.rand(8, 50, 50+24);

    var note_size = note.size;
    var x;//temp variable for Synth

    "-----START-----\n".post;
    note.postln;
    "---------------\n".post;

    (note_size - 1).do({|item,i|
        (0 .. note_size - 2 - i).do({|jtem,j|

            //"compare: (%, %)\n".postf(jtem,jtem+1);


            if(note[jtem] > note[jtem+1],{
                note.swap(jtem,jtem+1);
                note.postln;

                note.do({|ktem,k|
                    x = Synth(\default,[\freq,ktem.midicps]);
                    (1/8).wait;
                    x.release(0.1);
                });
            });//end of if


        });//end of j in do-loop
    });//end of i in do-loop

    "-----END-----\n".post;
    note.postln;
});
)

~r.reset;
~r.play;

コード実行例

-----START-----
[ 60, 65, 70, 53, 69, 52, 56, 55 ]
---------------
[ 60, 65, 53, 70, 69, 52, 56, 55 ]
[ 60, 65, 53, 69, 70, 52, 56, 55 ]
[ 60, 65, 53, 69, 52, 70, 56, 55 ]
[ 60, 65, 53, 69, 52, 56, 70, 55 ]
[ 60, 65, 53, 69, 52, 56, 55, 70 ]
[ 60, 53, 65, 69, 52, 56, 55, 70 ]
[ 60, 53, 65, 52, 69, 56, 55, 70 ]
[ 60, 53, 65, 52, 56, 69, 55, 70 ]
[ 60, 53, 65, 52, 56, 55, 69, 70 ]
[ 53, 60, 65, 52, 56, 55, 69, 70 ]
[ 53, 60, 52, 65, 56, 55, 69, 70 ]
[ 53, 60, 52, 56, 65, 55, 69, 70 ]
[ 53, 60, 52, 56, 55, 65, 69, 70 ]
[ 53, 52, 60, 56, 55, 65, 69, 70 ]
[ 53, 52, 56, 60, 55, 65, 69, 70 ]
[ 53, 52, 56, 55, 60, 65, 69, 70 ]
[ 52, 53, 56, 55, 60, 65, 69, 70 ]
[ 52, 53, 55, 56, 60, 65, 69, 70 ]
-----END-----
[ 52, 53, 55, 56, 60, 65, 69, 70 ]

学んだことなど

  • noteが配列のとき、note.swap(i , j)note[i]note[j]を交換する。
  • do-loopの反復回数に悩んだ。n.do(0 .. n).doだと、前者はn回反復し後者はn+1回反復する。

(
var x = 0;
(8).do({|item,i|
    /* "(8).do({" is same as "8.do({" */
    x = x+1;
    "(item,i) = (%, %)\n".postf(item,i);
});
x;//8
)

/* 実行結果
(item,i) = (0, 0)
(item,i) = (1, 1)
(item,i) = (2, 2)
(item,i) = (3, 3)
(item,i) = (4, 4)
(item,i) = (5, 5)
(item,i) = (6, 6)
(item,i) = (7, 7)
*/

(
var x = 0;
(0 .. 8).do({|item,i|
    x = x+1;
    "(item,i) = (%, %)\n".postf(item,i);
});

x;//9
)

/* 実行結果
(item,i) = (0, 0)
(item,i) = (1, 1)
(item,i) = (2, 2)
(item,i) = (3, 3)
(item,i) = (4, 4)
(item,i) = (5, 5)
(item,i) = (6, 6)
(item,i) = (7, 7)
(item,i) = (8, 8)
*/
  • 前述のdo-loop例を頭に入れて、今回のコードのループ構造を見ると、以下の通りとなる。
(
var note = Array.rand(8,50,50+12+12);
var note_size = note.size;

"-------------------".postln;
" - note is %\n".postf(note);
" - note_size is %\n".postf(note_size);
"-------------------".postln;

(note_size - 1).do({|item,i|
    (0 .. note_size - 2 - i).do({|jtem,j|
        "item, i : jtem, j = %, % : %, %\n".postf(item,i,jtem,j);
    });
});
)

//実行結果
/*
-------------------
 - note is [ 54, 50, 52, 61, 55, 50, 70, 65 ]
 - note_size is 8
-------------------
item, i : jtem, j = 0, 0 : 0, 0
item, i : jtem, j = 0, 0 : 1, 1
item, i : jtem, j = 0, 0 : 2, 2
item, i : jtem, j = 0, 0 : 3, 3
item, i : jtem, j = 0, 0 : 4, 4
item, i : jtem, j = 0, 0 : 5, 5
item, i : jtem, j = 0, 0 : 6, 6
item, i : jtem, j = 1, 1 : 0, 0
item, i : jtem, j = 1, 1 : 1, 1
item, i : jtem, j = 1, 1 : 2, 2
item, i : jtem, j = 1, 1 : 3, 3
item, i : jtem, j = 1, 1 : 4, 4
item, i : jtem, j = 1, 1 : 5, 5
item, i : jtem, j = 2, 2 : 0, 0
item, i : jtem, j = 2, 2 : 1, 1
item, i : jtem, j = 2, 2 : 2, 2
item, i : jtem, j = 2, 2 : 3, 3
item, i : jtem, j = 2, 2 : 4, 4
item, i : jtem, j = 3, 3 : 0, 0
item, i : jtem, j = 3, 3 : 1, 1
item, i : jtem, j = 3, 3 : 2, 2
item, i : jtem, j = 3, 3 : 3, 3
item, i : jtem, j = 4, 4 : 0, 0
item, i : jtem, j = 4, 4 : 1, 1
item, i : jtem, j = 4, 4 : 2, 2
item, i : jtem, j = 5, 5 : 0, 0
item, i : jtem, j = 5, 5 : 1, 1
item, i : jtem, j = 6, 6 : 0, 0
*/

参考

バブルソート : アルゴリズム

Array | SuperCollider 3.9dev Help

ハノン練習曲 第1番

Scaleを使ってハノン練習曲 第1番(=ドミファソラソファミ〜♪)をプログラミングしてみる。

分析

degrees(度数)*1[0,2,3,4,5,4,3,2]に固定してプログラミングしたい。ということでどのスケールを使うか検討する。

まず「ドミファソラソファミ」はメジャースケールに見える。そこで「レファソラシラソファ」以降も全てメジャースケールである、と早合点してはならぬ。例えば「ドミファソラソファミ」をハ長調と見なしてニ長調に移調すると、「レファソラシラソファ」ではなく「レファ#ソラシラソファ#」となる。これだとずいぶんと明るめなハノンになってしまう。

では、全てがピアノの白鍵になるような都合の良いスケールと言えば・・・教会旋法(チャーチモード)である。なので、「ドミファソラソファミ」がionian、「レファソラシラソファ」がdorian、と教科書通りチャーチモードを並べていけばdegreesを[0,2,3,4,5,4,3,2]に固定してハノンを奏でることができる。

以上を表にまとめるとこのようになる。

実音 Scale root(MIDI note) degrees
ドミファソラソファミ ionian 60 [0,2,3,4,5,4,3,2]
レファソラシラソファ dorian 62 [0,2,3,4,5,4,3,2]
ミソラシドシラソ phrygian 64 [0,2,3,4,5,4,3,2]
ファラシドレドシラ lydian 65 [0,2,3,4,5,4,3,2]
ソシドレミレドシ mixolydian 67 [0,2,3,4,5,4,3,2]
ラドレミファミレド aeolian 69 [0,2,3,4,5,4,3,2]
シレミファソファミレ locrian 71 [0,2,3,4,5,4,3,2]

この表に基づいてプログラミングしてみる。

コード

//Instrument
(
SynthDef(\h,{
    |amp=0.3,gate=1,out=0,freq=440|
    var sig,e,eg;

    sig = SinOsc.ar(freq);
    e = Env.adsr;
    eg = EnvGen.ar(e,gate,doneAction:2);
    sig = sig * eg;
    Out.ar(out,Pan2.ar(sig,0,amp));
}).add;
);


//Score
(
~r = r({
    var root  = [60,62,64,65,67,69,71];//C D E F G A H
    var hanon_up   = [0,2,3,4,5,4,3,2];//Hanon No.1 going up
    var hanon_down = [5,3,2,1,0,1,2,3];//Hanon No.1 going down
    var mode  = #[
        ionian,dorian,phrygian,
        lydian,mixolydian,
        aeolian,locrian
    ];

    var x;


    /* Hanon Up */
    mode.do({|i,ix|
        hanon_up.do({|j|
            x = Synth(\h,
                [\freq,Scale.at(i).degreeToFreq(j,root[ix].midicps,0)]
            );
            (1/4).wait;
            x.release(0.1);
        });
    });


    /* Hanon Down */
    mode = mode.reverse ++ #[locrian];
    root = root.reverse ++ [59];
    mode.do({|i,ix|
        hanon_down.do({|j|
            x = Synth(\h,
                [\freq,Scale.at(i).degreeToFreq(j,root[ix].midicps,0)]
            );
            (1/4).wait;
            x.release(0.1);
        });
    });


    /* Ending */
    x = Synth(\h,[\freq,60.midicps]);
    2.wait;
    x.release(0.1);

});
);


//Exec
~r.play;
~r.reset;

解説

  • Scale.major == Scale.at(\major);//true

  • #[ionian,dorian,/*略*/ locrian]と書くと、配列の中身はリテラルとなる。リテラルになると何が利点になるのか、はよく分からないが「In literal Arrays names are interpreted as symbols.」*2とのことなので、配列の中身はシンボル扱いされるらしい。シンボルとキーの違いは・・・。(おそらく言葉の使い方としては、シンボルをキーとして扱う、と呼称すると思われる。それ以上の意味は分からない。。)

  • mode.reverse ++ #[locrian];はmodeを逆順にした上でlocrianを最後に追加している。この前学習した通り++と書くと配列は直和される。なお、これを+と書くと#[locrian]は無視される。

参考

ハノン練習曲 第1番

教会旋法

*1:SuperColliderでは、degreesは1からではなく0から始まる。

*2:http://doc.sccode.org/Reference/Literals.html

純正音程の完全四度堆積

純正音程の完全四度は振動比が3:4である。たとえば、MIDIノートの60(振動数およそ261[Hz]、中央ド)の純正音程版の完全四度は、261×(4/3) = 348となる。なので、完全四度をずっと堆積することは基準の振動数(周波数)に4/3を乗算し続ければ良い。良いのではあるが、4/3 > 1なので3回ぐらい掛け算すると積が2を超えてしまう。2を超えるとオクターブ上がってしまう。

//(4/3)の0乗,1乗,...,12乗を小数点2桁まで求める
( (4/3)**(0..12) ).round(0.01);
-> [ 1, 1.33, 1.78, 2.37, 3.16, 4.21, 5.62, 7.49, 9.99, 13.32, 17.76, 23.68, 31.57 ]

ということで、2を超えたら2で割ってオクターブ下げる処理を施しながら完全四度堆積をしてみることにする。

(
~r = r({
    var f=1;
    f.yield;
    loop({
        f = f * (4/3);
        if(f > 2,{ f = f/2 },{ f });
        f.yield;
    });
});
)

~r.reset;
~r.nextN(12).round(0.01);

-> [ 1, 1.33, 1.78, 1.19, 1.58, 1.05, 1.4, 1.87, 1.25, 1.66, 1.11, 1.48 ]

中央ドから完全四度堆積するコード

(
SynthDef(\h,{
    |amp=0.1,freq=440|
    var sig,e,eg;
    
    e = Env.perc;
    eg = EnvGen.kr(e,doneAction:2);
    sig = SinOsc.ar(freq);
    sig = sig * eg;
    
    Out.ar(0,Pan2.ar(sig,0,amp));
}).add;
)


(
~r = r({
    var f = 1,c = 60.midicps;
    Synth(\h,[\freq,c*f]);
    1.wait;

    inf.do({
        f = f * (4/3);
        if( f > 2, {f = f/2});
        (c*f).postln;
        Synth(\h,[\freq,c*f]);
        1.wait;
    });
});
)

参考

Wikipedia:完全四度