axjack's blog

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

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.

gateを0にしてreleaseする

Envelopeにてgate=0でreleaseを作動させるには、SynthDefのargにgateを設定し、なおかつEnvGenにgateを渡す必要がある。これを怠ると、当然ながら.set(\gate,0)と実行しても音は止まない。

doneAction:2と設定していると、gateを0にしreleaseが終わったタイミングで、実行したSynthがGroupeから消える。なので、再び実行するときはSynthをServerにnewなりnewPausedなりする必要がある。

gate
This triggers the envelope and holds it open while > 0. If the Env is fixed-length (e.g. Env.linen, Env.perc), the gate argument is used as a simple trigger. If it is an sustaining envelope (e.g. Env.adsr, Env.asr), the envelope is held open until the gate becomes 0, at which point is released. If gate < 0, force release with time -1.0 - gate. See EnvGen: Forced%20release example.

作ったコード

(s.meter(0,2);
s.plotTree;
)

(SynthDef(\tri,
    {|out=0,gate=1,amp=0.1,freq=440|
        var sig,e,eg;
        e = Env.adsr(attackTime:1,decayTime:3,sustainLevel:0.5,releaseTime:3,peakLevel:1,curve:-8);
        eg = EnvGen.kr(e,gate,amp,0,1,2);
        sig = LFTri.ar(freq);
        sig = sig * eg;
        Out.ar(out,sig);
    }
).add;
)


~x = Synth(\tri,[\freq,500]);
~x.set(\gate,0);

参考

Env | SuperCollider 3.9dev Help

Busの練習

Busを介して音を出す、はずであった。作ったコードのoBusiBusでそれらしいことをしたつもりである。しかし、どちらかというと

//use one bus
(
~b1 = Bus.audio(s,1);
{Out.ar(0,In.ar(~b1,1))}.play;
{Out.ar(~b1,SinOsc.ar(440))}.play;
)

//use two busses
(
~b1 = Bus.audio(s,1);
~b2 = Bus.audio(s,1);
{Out.ar(0,In.ar(~b1,1))}.play;
{Out.ar(~b1,In.ar(~b2,1))}.play;
{Out.ar(~b2,LFTri.ar(440))}.play;
)

と明示的にBusを作った方が良かったと最後になって気付いた。

メモ

  • Synth.newPausedで一時停止した状態のSynthを作ることが出来る。~x = Synth.newPaused(...)を実行した後に~x.runと実行すると再生することが出来る。

  • Synth.after(aNode,defName)でaNodeの後ろにSynthを作ることが出来る。source -> effectのような流れを作る場合に使う。

  • 左右の信号に掛けるLFOの位相をそれぞれ変え(例:左は0, 右はpi/2)ておくと、位相のズレを体感することが出来る(論より証拠)。

作ったコード

s.meter(0,2);
s.plotTree;

//Triangle Osc
(
SynthDef(\tri,{
    |freq = 440,oBus = 10|
    var out,sig;
    sig = Mix.fill(4,{|i|LFTri.ar(freq/(1+i),0,1/4)});
    out = sig;
    Out.ar(oBus,out);
}).add;
)


//Effector
(
SynthDef(\eff,{
    |iBus = 10,afreq=1|
    var in,in_r,in_l;
    in = In.ar(iBus);
    in_r = in * SinOsc.kr(afreq,0,0.2,0.4);
    in_l = in * SinOsc.kr(afreq,pi/2,0.2,0.4);
    Out.ar(0,[in_r,in_l]);
}).add;
)

//kick
(
SynthDef(\kick,{
    var env = EnvGen.ar(Env(levels:[100,200,60,0],times:[0.01,0.2,0.2,0.2],curve:-10));
    var sig = SinOsc.ar(env,0,1,0) + WhiteNoise.ar(0.01);
    sig = sig * EnvGen.ar(Env.perc(attackTime:0,releaseTime:0.3),doneAction:2);
    Out.ar(0,sig!2);
}).add;
)

//Synth
(
~tri = Synth.newPaused(\tri,[\freq,200]);
//~eff = Synth(\eff);
~eff = Synth.after(~tri,\eff);
)

//run Synth
~tri.run;

//Routine
(
~r = Routine({
    var n = 10;
    loop{
    n.do({|i|
        ~eff.set(\afreq,(i+1));
            (i+1).postln;
        4.wait;
    });
    }
});

~rk = r({loop{Synth(\kick);1.wait;}});

)


(
~rk.play();
~r.play();
)

参考

Bus | SuperCollider 3.9dev Help

Order of execution | SuperCollider 3.9dev Help

Synth | SuperCollider 3.9dev Help

授業料無償化と発言したのは本当なのだろうか?

授業料無償化?

Twitterが授業料無償化(教育費無償化)で有象無象のごった煮となっていたので、ソースなどを辿る旅に出た記録。

According to NHK

大学在学中は授業料無償化 自民が検討案まとめる | NHKニュース

NHKニュースサイトの動画を見たが、ニュースキャスターの声が大きすぎて自民党教育再生実行本部?の生の声が聞こえなかった。果たして本当に「無償化」と発言したのだろうか?

教育再生実行本部

教育再生実行本部 第八次提言 | 政策 | ニュース | 自由民主党

教育再生実行本部の提言は、サイトを確認すると第八提言(2017年5月18日)は存在するものの(おそらく今回に相当する)第九提言はまだ存在しない。「無償化」発言のエビデンスを追うにはもう少し時間を待つ必要があるかもしれない。

自民党の政策パンフレット

政策BANK2017

「無償化」では無いが「卒業後拠出金方式」という単語は自民党の政策BANK2017のp.32に記述がある。オーストラリアのHECSを参考にした仕組みらしい。

ベネッセ

教育費負担をどう考えるか|ベネッセ 教育情報サイト

直接は関係ないが教育費無償化については、ベネッセのコラムが分かりやすかった。

考えたこと

完全無償化となると財源どうするの?となるし、卒業後拠出金方式だとすると大学の経営不安定になるような気がする。

いずれにしても現状維持より良い選択肢・現状維持より魅力的な解決策には見えないのが残念である。

全然関係ない余談

はてなブログmarkdownモードにおいて、リンクを挿入する際[URL:title]だとtitleが自動挿入される。

がしかし、URL参照先がpdfだとtitleを取得してくれないようである。

なので手動でタイトルを打ち込む必要がある。その方法は以下の2つである。

  • [URL:title=タイトル]
  • [タイトル](URL)

上ははてな専用の書き方で、下は一般的なmarkdownの書き方である。pdfのリンク張るときは少し注意。

換気扇の音を作りたい

どうも音が籠もってしまう。かといってハイパスだとシャリシャリ感が邪魔である。 目的の音とは違うが、waveの音は冷蔵庫から鳴る低音に近い。

コード

(
SynthDef(\kanki,
{
    |cfreq = 388,amp=0.4|
    var noise,wave,out;
    noise = RLPF.ar(BrownNoise.ar(0.5),cfreq,0.5);
    wave = RLPF.ar(Saw.ar(cfreq/8,0.5),cfreq,0.3);
    out = noise + wave;
    Out.ar(0,Pan2.ar(out,0,amp));
}).add;
)

~x = Synth(\kanki);

PseqにasStreamを付けるとRoutineになる

Pseqに限らずPrandとかPbrownでも.asStream付けるとRoutineになる。PatternをRoutineの中で使うときに重宝する、のだろうか?

なお、Patternに加えてEnvも.asStream付けてRoutine化することが可能。

コード

/////////////////////////////////
//Routine
(
~r = Routine(
    {
        2.do({
            12.yield;
            34.yield;
            56.yield;
        });
    });
)


~r.reset;
(
{
    7.do({
        ~r.next.postln;
        1.wait;
    });
}.fork
)

/////////////////////////////////
//Pseq
~p = Pseq([12,34,56],2).asStream;

~p.reset;
(
{
    7.do({
        ~p.next.postln;
        1.wait;
    });
}.fork
)

参考

Pseq | SuperCollider 3.9dev Help

http://doc.sccode.org/Classes/Pattern.html#-asStream

http://doc.sccode.org/Classes/Env.html#-asStream

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