axjack's blog

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

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:完全四度

ERROR: syntax error, unexpected VAR, expecting $end を追う

きっかけ

次のコードを選択して実行するとエラーになる。

コード1

var aa;
aa = 1;
var bb;
bb = 1 + aa;

エラー

ERROR: syntax error, unexpected VAR, expecting $end
  in file 'selected text'
  line 3 char 3:

  var bb;
  ^^^
  bb = 1 + aa; 
-----------------------------------
ERROR: Command line parse failed
-> nil

"シンタックスエラーなり。予期せぬVARがある。そこは終端記号があるべき。場所は4行目云々。"と読める。

一方、こちらの場合は問題なく通る。

コード2

var aa;
var bb;
aa = 1;
bb = 1 + aa;

出力結果

-> 2

どうやら、var宣言した後に「式」を書いて、その後にまたvar宣言するとエラーになるらしい。斯くして、var宣言はまとめて書いた後に「式」を書けば良いという教訓を得た。

のは良いが、どうせならということでSuperColliderのソースを追ってどの辺りでエラーになったのかを確かめてみよう。

コードを追う

まずは検索

githubsupercolliderの中をsyntax error, unexpectedで検索する。するとlang/LangSource/Bison/lang11d_tab.cppあたりで引っかかっているのを発見。

このファイルはどのフォルダに入っているのかを見ると、

supercollider/lang/LangSource/Bison/
    lang11d
    lang11d_tab.cpp
    lang11d_tab.h
    make_parser.sh

となっている。make_parser.shというシェルスクリプトで何やら生成しているのだろう。また「make parser」、ということはパーサー(構文解析器)をメイク(作る)に違いない。このファイルを見ると、

#!/bin/sh
bison -o lang11d_tab.cpp lang11d

と記述してある。意味的には「bisonを実行、出力ファイルはlang11d_tab.cppで入力ファイルはlang11d」であろう。ということなので、肝となるのは入力ファイルのlang11dに違いない。

bisonとは?

bisonとはWikipediaによると、

Bison(バイソン)とは構文解析器を生成するパーサジェネレータの一種であり、CコンパイラとしてのGCCのサポートのために開発されたフリーソフトウェアである。

とのこと。このことから、lang11dは構文解析機を生成するための入力ファイルであることが分かる。つまりSuperColliderのsyntaxはここで定義されているのだろう、と認識してみることにする。

該当箇所を追う

以下、lang11dから該当しそうな箇所を引用しながらコードを追う。

VARを探す

class宣言の方にもVARが見つかったけれど、そちらではなく以下の定義が怪しい。怪しいというのは、普段使っているVARの定義っぽいということである。

funcvardecl  : VAR vardeflist ';'
                { $$ = (intptr_t)newPyrVarListNode((PyrVarDefNode*)$2, varLocal); }
;

これは、「funcvardeclとは、VARというtokenに、vardeflistと';'が続くものである」と読める。「VARに続く次の文字列はvardeflistである」とも読める。では、vardeflistは?というと、

vardeflist      : vardef
                        | vardeflist ',' vardef
                                { $$ = (intptr_t)linkNextNode((PyrParseNode*)$1, (PyrParseNode*)$3); }
;

「vardeflistとは、vardefもしくはvardeflistに','とvardefと表されるものである」と読める。再帰的な定義である。vardefにカンマを打ちvardefを更に書いてもvardeflistと認める、ということだろうか?

では、vardefとはなんぞや?と探してみると、

vardef          : name
                                { $$ = (intptr_t)newPyrVarDefNode((PyrSlotNode*)$1, NULL, 0); }
                        | name '=' expr
                                { $$ = (intptr_t)newPyrVarDefNode((PyrSlotNode*)$1, (PyrParseNode*)$3, 0); }
                        | name '(' exprseq ')'
                                {
                                    PyrParseNode* node = (PyrParseNode*)$3;
                                    node->mParens = 1;
                                    $$ = (intptr_t)newPyrVarDefNode((PyrSlotNode*)$1, node, 0);
                                }
;

「vardefとは、nameまたはname = exprまたはname(expreseq)である」とのこと。ここで、nameは

%token NAME INTEGER SC_FLOAT ACCIDENTAL SYMBOL STRING ASCII PRIMITIVENAME CLASSNAME CURRYARG

にあるようにtoken(終端記号)である。exprとexprseqはいわゆる式である。長いので引用は割愛する。

ひとまずまとめると、lang11dから普段使うVARのようなものを調べた結果、VAR v1(,v2,v3,...,vn);のうち、v1(,v2,v3,...,vn)の部分をfuncvardeclと呼ぶことが分かった。次にfuncvardeclを見てみる

funcvardeclを探す

funcvardeclのすぐ上に幾つか見つかる。

funcvardecls : { $$ = 0; }
                | funcvardecls funcvardecl
                    { $$ = (intptr_t)linkNextNode((PyrParseNode*)$1, (PyrParseNode*)$2); }
                ;

funcvardecls1   : funcvardecl
                | funcvardecls1 funcvardecl
                    { $$ = (intptr_t)linkNextNode((PyrParseNode*)$1, (PyrParseNode*)$2); }
                ;

funcvardecl : VAR vardeflist ';'
{ $$ = (intptr_t)newPyrVarListNode((PyrVarDefNode*)$2, varLocal); }

funcvardeclを並べたものをfuncvardecls・funcvardecls1と呼ぶ。では、funcvardeclsは・・・と更に辿ると面白い箇所を発見した。

cmdlinecode  : '(' funcvardecls1 funcbody ')'
                { $$ = (intptr_t)newPyrBlockNode(NULL, (PyrVarListNode*)$2, (PyrParseNode*)$3, false); }
            | funcvardecls1 funcbody
                { $$ = (intptr_t)newPyrBlockNode(NULL, (PyrVarListNode*)$1, (PyrParseNode*)$2, false); }
            | funcbody
                { $$ = (intptr_t)newPyrBlockNode(NULL, NULL, (PyrParseNode*)$1, false); }
;

cmdlinecodeの定義である。中括弧内を省略して書くと、

cmdlinecode  : '(' funcvardecls1 funcbody ')'
            | funcvardecls1 funcbody
            | funcbody
;

となる。cmdlinecodeは察するにコマンドラインコードであろう。IDEにコードを書いて選択して実行することを指すものだと思われる。そして、その定義というか受理される文法は

  • '(' funcvardecls1 funcbody ')'
  • funcvardecls1 funcbody
  • funcbody

の3タイプしかない。念のためfuncbody(おそらく関数本体を意味する)を見ると、

funcbody : funretval
            | exprseq funretval
                { $$ = (intptr_t)newPyrDropNode((PyrParseNode*)$1, (PyrParseNode*)$2); }
;

funretval(おそらく関数戻り値を意味する)を見ると、

funretval    :
            { $$ = (intptr_t)newPyrBlockReturnNode(); }
        | '^' expr optsemi
            { $$ = (intptr_t)newPyrReturnNode((PyrParseNode*)$2); }
;

となっている。つまり、funcbodyにVARは含まれていないことが分かる。

まとめ

VAR宣言はfuncbodyより前に書かれていないとcmdlinecodeとしては受理されず、よってsyntax error扱いされると思われる。斯くして、var宣言はまとめて書いた後に「式」を書けば良いという教訓は妥当であると言えよう。

文字列処理

電話番号のような文字列からハイフンを取り除き、一文字ずつ数字にしたものを要素として持つ配列を返す関数を作ろうとしたらエラーで泣いたので整理する。泣いている暇があったらドキュメントをしっかり読もう。

基本

.inspectで型*1を調べることができる。'で囲むとCharではなくSymbolになるので要注意。

3.inspect;//Integer
"3".inspect;//String
'3'.inspect;//Symbol
\3.inspect;//Symbol
$3.inspect;//Char
[3].inspect;//Array
(3).inspect;//Interger
{3}.inspect;//Function

==演算

型が違うと==falseとなる。ここで、String

String represents an array of characters.

と説明されていることから(そして実際にも)、それぞれの要素はCharである。なので、要素を参照する時は見た目でIntegerだと思ってはならない。

3 == 3;//true: Integer vs Integer
3 == "3";//false: Integer vs String
3 == '3';//false: Integer vs Symbol
3 == \3 ;//false: Integer vs Symbol
'3' == "3";//false: Symbol vs String
'3' == \3 ;//true: Symbol vs Symbol
"3"[0] == 3;//false: Char vs Integer
"3"[0] == $3;//true: Char vs Char
"3".at(0) == $3;//true: Char vs Char
[3][0] == 3;//true: Integer vs Integer
[3].at(0) == 3;//true: Integer vs Integer
{3} == 3;//false: Function vs Integer
{3}.() == 3;//true: Integer vs Integer
{3}.(3) == 3;//true: Integer vs Integer
(3) == 3;//true: Integer vs Integer
(3).() == 3;//true: Integer vs Integer

+演算と++演算

+演算子は同一index同士で和を取るのに対して、++演算子は直和である。使えそうな小技としては、[] ++ "123";//-> [ 1, 2, 3 ]。この式で文字列から配列に変換できる。

[1,2,3] + [4,5,6];//-> [ 5, 7, 9 ]
[1,2,3] ++ [4,5,6];//-> [ 1, 2, 3, 4, 5, 6 ]

"123" + [];//-> 123 [  ]
"123" ++ [];//-> 123[  ]
[] + "123";//ERROR: Message '+' not understood.
[] ++ "123";//-> [ 1, 2, 3 ]
[1,2,3] + "";//ERROR: binary operator '+' failed.
[1,2,3] ++ "";//-> [ 1, 2, 3 ]
"" + [1,2,3];//->  [ 1, 2, 3 ]
"" ++ [1,2,3];//-> [ 1, 2, 3 ]

("foo" + "bar");//-> foo bar
"foo" ++ "bar";//-> foobar
"foo" + $b;//-> foo b
"foo" ++ $b;//-> foob
$b + "foo";//ERROR: Message '+' not understood.
$b ++ "foo";//-> bfoo

本題

やりたかったことは、「電話番号のような文字列からハイフンを取り除き、一文字ずつ数字にしたものを要素として持つ配列を返す」ことである。CharをIntegerにする、たとえば$55へと変換するには$5.digitとすれば良い。以上を踏まえてコードを書くと以下の通り。

コード

(
f = {|tel|
    var arrayfy = [] ++ _ ;
    var selecter = _.select( _ != $- );
    var collecter = _.collect( _.digit );
    (collecter <> selecter <>  arrayfy).(tel);
}
)

f.("0190-2345-6789");//-> [ 0, 1, 9, 0, 2, 3, 4, 5, 6, 7, 8, 9 ]
f.("881-902-22339-1");//-> [ 8, 8, 1, 9, 0, 2, 2, 2, 3, 3, 9, 1 ]
f.("987-8765-4321");//-> [ 9, 8, 7, 8, 7, 6, 5, 4, 3, 2, 1 ]

参考

Char | SuperCollider 3.9dev Help

String | SuperCollider 3.9dev Help

*1:正式名称がよくわからない

幾つかの音を適当なタイミングで鳴らして適当なタイミングで止める

音を止めるには、Synth Nodeに対して

  • gate=0を投げてreleaseさせる
  • freeする

以外にもありそうだが、今のところ知っているのはこの二つである。

Synth NodeというかSynth NodeのNodeID

Synth Nodeには一意のIDが振られている。よって、NodeIDを指定してしまえばメッセージを送ることができる。

SynthDefで定義せず {SinOsc.ar(440,0,0.1)}.play; のように関数からplayした場合、

f:id:axjack:20171106215458p:plain

f:id:axjack:20171106215443p:plain

のように名無し楽器が作られ、NodeIDが振られる。(今回はid:1005)

この状態から音を止めるには、以下のようにSeverにメッセージを送る。

s.sendMsg('/n_free',s.nextNodeID - 1);
//s.nextNodeIDは1005の「次の」1006となる

本題

「幾つかの音を適当なタイミングで鳴らして適当なタイミングで止める」ことを書いてみる。

コード

//meter and tree
(s.meter(0,2);s.plotTree;)


//Synth
(SynthDef(\ins,{
    |amp=0.2,freq=440,gate=1|
    var sig,e,env;
    sig = LFTri.ar(freq);
    e = Env.adsr(attackTime:2,decayTime:0.5,sustainLevel:0.3,releaseTime:3);
    env = EnvGen.kr(e,gate,doneAction:2);
    sig = sig * env;

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


//Routine
(
var synth;
var nodes = List.new;
var n = 5;
~r = Routine({
    n.do({
        synth = Synth(\ins,[\freq,rrand(60,72).midicps]);
        nodes.add(synth.nodeID);
        0.3.wait;
    });

    n.do({
        s.sendMsg('/n_set',nodes.pop,\gate,0);
        rrand(1,10).wait;
    });
});
)


//play
~r.reset;
//~r.next;
~r.play(TempoClock(2));

学んだこと

  • Synthの戻り値はオブジェクト*1であることから、synth.nodeIDで作成したSynthのnodeIDを取得することができる。
  • nodes.add(synth.nodeID);でnodeIDをnodesに格納する。ここで、nodesはArrayではなくListである。
  • ListはexpandableなCollectionに、ArrayはfixedなCollectionに使うと良い*2addメソッドはListには存在する。Arrayにも存在はするがaddらしくない挙動をするので要注意。
  • nodes.popはListの末尾をpopしてdeleteする。

参考

Node | SuperCollider 3.9dev Help

Server Command Reference | SuperCollider 3.9dev Help

Array | SuperCollider 3.9dev Help

List | SuperCollider 3.9dev Help

*1:要確認

*2:Arrays are ArrayedCollections whose slots may contain any object. Arrays have a fixed maximum size beyond which they cannot grow. For expandable arrays, use the List class.

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