SuperCollider Code Reading

033. (Plugins) ランダムなfloat値(-1.0〜1.0)

Posted in SuperCollider Code Reading on 4月 29th, 2008 by Norihisa Nagano – Be the first to comment

ひさびさ,SCCR33回目.

今回は番外編.Plugins.xcodeprojを見ます.
こちらはPlugin(UGen)用のprojectで,DSPコードがもりもり書いてあります.

今回はランダムなfloat値(-1.0〜1.0)を得る方法について.

ランダムなfloat値はAudioをやってると結構使うのですが,Cのライブラリにはfloatのランダムは無い(はず).

float f = (rand() % 1000) / 500.01.0;

などと,何も考えずにやってもいいのですが,限界まで精度を使うのに最適な数値はいくつなのか,等々疑問がたくさんです.
というわけで,Audio処理で頻繁に使うfloat型を解明する.

まず,浮動小数点について.

http://ja.wikipedia.org/wiki/浮動小数点数

浮動小数点数(ふどうしょうすうてんすう)は、コンピュータにおける実数の近似値の表現方式。

らしい.123.456の”.”の位置が変わるということ.
逆は固定小数点.

浮動小数点のbit表現については色々あってややこしいのですが
こういう時はこうなると説明されているものの方が分かりやすい(んじゃないかな).

というわけで解説を試みる.

-0.333333

のbit表現は

1 01111101 01010101010101010011111

になる.

この読み方

31bit目は符号部(1bit)
1だとマイナス.0ならプラス.今回は1なのでマイナス.

次は指数部(8bit)
8bitなので0〜255が表現できます.
で,この指数部の数値 – 127した値が指数になる.
ここでは01111101は125なので,125 – 127で-2になる.
2 ^ -2(指数) = 0.25000
となる.
127を127バイアスと言うらしい.

次の01010101010101010011111
は仮数部という.(23bit)
これには暗黙の23bit目”1″を追加することになっている.
そうすると

1.01010101010101010011111

になり

(2進数の小数点以下の計算をする)
1 * 2 ^ 0乗 + (0 * 2 ^ -1) + (1 * 2 ^ -2) + (0 * 2 ^ -3)….とやっていくと
これは1.33332になる.

で,仮数部と指数部を掛ける

1.33332 * 0.25000 = 0.333333

符号部が1 == マイナス なので

-0.333333

になる.おおお〜

ということで,指数部次第でいろいろな数値が表現できるということが分かります.

floatはビット演算できません

これまでの解説が本当か検証する.
が,floatの値はDebuggerで追ってもbit表現が見られないんですね.
なんでだよ.で,しかもbit演算できません.

float hoge = 1.0;

hoge < < 2;

はコンパイルエラー.

えー,なんでですか!という感じですが,共用体を使う裏技が存在します.

共用体とは何か

共用体とは、2つ以上の変数が、同じメモリ領域を共有するという構造のことです。

らしい.

ということは,intとfloatをメンバーに持つ共用体を作ってfloatの値を代入後,intとしてアクセスすればfloat値をビット演算できる.

#import <Foundation/Foundation.h>

typedef union fi

{

    UInt32 i;

    float f;

}fi;

int main (int argc, const char * argv[]) {

    fi u;

    u.f = -0.333333;   

    return 0;

}

こういうコードを書いてDebuggerで追うと
sc_33_1.png

おお,↑の通りのビットパターンになっています.

さて,本題.
ここでSuperColliderの乱数生成コードを見てみます.
Plugins.xcodeproj -> SC_RGen.h: 263にこういうコードがあります.

inline float frand2( uint32& s1, uint32& s2, uint32& s3 )

{

    // return a float from -1.0 to +0.999…

    union { uint32 i; float f; } u;

    u.i = 0×40000000 | (trand(s1,s2,s3) >> 9);

    return u.f3.f;

}

trand()はunsigned int 3個からunsigned intを返す疑似乱数関数です.

0×40000000は30bit目が1.
(0100 0000 0000 0000 0000 0000 0000 0000)

これはつまり,指数部が10000000ということで
10000000 == 128
128 – 127 = 1.
2 ^ 1は2.

>> 9しているということは,32bit -> 23bitに落としている.
ということで,これは仮数部.

OR演算しているので,どっちかが1なら1.

仮にtrandが32bitの最大値を返したとすると

0100 0000 0000 0000 0000 0000 0000 0000

           111 1111 1111 1111 1111 1111

でORなので

0100 0000 0111 1111 1111 1111 1111 1111

となる.
これは,指数部が2,仮数部も2.なので4になる.(この場合符号部は常に0 == プラス)

仮数部のビットが全てゼロだと,暗黙の23bit目が1なので 1.00000…(二進数)となって2(指数部) * 1(仮数部) = 2になる.

というわけで,2〜4の範囲をとることになる.

UInt32 i = (1L << 32) – 1;

union { UInt32 i; float f; } u;

u.i = 0×40000000 | (i >> 9);    

NSLog(@”u.f = %f”,u.f);

u.f = 4.000000



UInt32 i = 0;

union { UInt32 i; float f; } u;

u.i = 0×40000000 | (i >> 9);    

NSLog(@”u.f = %f”,u.f);

u.f = 2.000000

-3.0引いて-1.0〜1.0.
エレガント〜

誤差とか浮動小数点の特殊な値(無限大とか)はすっとばしたので,詳し知りたい人はWrite Great Codeがお勧め.最高に細かく書いてあります.

このエントリーをはてなブックマークに追加

032. str4cpy 文字列のbit表現とバイトオーダー

Posted in SuperCollider Code Reading on 4月 11th, 2008 by Norihisa Nagano – Be the first to comment

SCCR32回目.

色々忙しくなってきて,まとめる時間が足りない.
Hashの章がめちゃ長くなりそげであります(現在35回目まで突入)

さて,そろそろ分からないAPIは無くなってきたのでOSCの続きに戻ります.

SC_CoreAudio.cpp: 191

bool ProcessOSCPacket(World *inWorld, OSC_Packet *inPacket)

{

    //scprintf(“ProcessOSCPacket %d, ‘%s’\n”, inPacket->mSize, inPacket->mData);

    if (!inPacket) return false;

    bool result;    

    inWorld->mDriverLock->Lock();

        SC_AudioDriver *driver = AudioDriver(inWorld);

        if (!driver) return false;

        inPacket->mIsBundle = gIsBundle.checkIsBundle((int32*)inPacket->mData);

        FifoMsg fifoMsg;

        fifoMsg.Set(inWorld, Perform_ToEngine_Msg, FreeOSCPacket, (void*)inPacket);

        result = driver->SendOscPacketMsgToEngine(fifoMsg);    

    inWorld->mDriverLock->Unlock();

    return result;

}

LockとUnLockは分かった.

SC_CoreAudio.cpp: 183

struct IsBundle

{

    IsBundle() { str4cpy(s, “#bundle”); }

    bool checkIsBundle(int32 *in) { return in[0] == s[0] && in[1] == s[1]; }

    int32 s[2];

};

IsBundle gIsBundle;

これは何だ.

まず,str4cpyから.
ざっと見た感じ,引数と関数名からdstにあたるのがint32[2] = 64bitであることから
最大8文字を32bit二個の数字に変換しているんじゃないかと思う.(charは8bitなので 64 / 8 = 8文字)

SC_Str4.cpp: 24

void str4cpy(int32 *dst, const char *src)

{

    char *cdst0 = (char*)(dst);

    char *cdst = cdst0;

    

    while (*src) {

        *cdst++ = *src++;

    }

    

    int charlen = cdst – cdst0;

    int pad = 4 – (charlen & 3);

    for (int i = 0; i < pad; ++i) {

        *cdst++ = 0;

    }

}


試しに”abcd”で試してみると

char *str = “abcd”;    

int s[2];

str4cpy(s,str);

‘a’ 01100001

‘b’ 01100010

‘c’ 01100011

‘d’ 01100100

なので

s[0]は

01100100 01100011 01100010 01100001

になるはず.

intel.png

実際そうなる.

d c b aになっている.

で,で,で,で,で,
実はこれはIntelの場合なのである.

PPCだと

ppc.png

a b c dになっている.

ががーん,
これは世に聞く,バイトオーダーというやつじゃないですか.

IntelはLittle Endian.
PPCはBig Endianで
それぞれ並び順が小さい桁の方から・大きい桁の方からbitを並べるのでそう呼ばれます.

http://developer.apple.com/jp/documentation/MacOSX/Conceptual/universal_binary/index.html
に詳しく書いてある.

注: 「ビッグエンディアン」と「リトルエンディアン」という用語は、ジョナサンスウィフトの18世紀の風刺小説「ガリバー旅行記」に由来します。ブレフスキュ帝国の臣民は、卵を大きいほうの端から食べる人々(ビッグエンディアン)と、小さいほうの端から食べる人々(リトルエンディアン)に分かれていました。

こういうネーミングセンスは非常によいなと思う :)

そんなもん,同じ規格にしとけよ!!という感じですが,しょうがないですね.
こういう違いがあることを知っておかないと,なんでIntelとPPCで挙動が違うのか分からないということになるので知っておきましょう.

めんどくさいので,以下はIntelだという前提で話を進める.

ではコードを追っていく.

int32は32bit
charは8bit
で,*charにキャストして,++でインクリメントすると

32bitの 0~ 7番目のbit
32bitの 8~15番目のbit
32bitの16~23番目のbit
32bitの24~31番目のbit

という風にアドレスが変わる

sc32_0-31.png

str4cpyをこういう風に書き換えてみると

    printf(“cdst0= %p\n”, cdst0);

    printf(” cdst= %p\n”, cdst);    

    while (*src) {

        *cdst++ = *src++;

    }    

    printf(“cdst0= %p\n”, cdst0);

    printf(” cdst= %p\n”, cdst);

例えば4文字だと

cdst0= 0xbffff8c0

 cdst= 0xbffff8c0

cdst0= 0xbffff8c0

 cdst= 0xbffff8c4

こういう風になる.(数字は毎度違うし,環境でも違う)
bffff8c0からbffff8c4になっているので,4進んだことが分かる.
cdst0は変わっていないことに注意.
これは先頭のアドレスを保持している.
な,の,で

int charlen = cdst – cdst0;

とやると,文字の長さが分かる.

ということで,srcをcdstにコピーして,文字数を計る処理をcharlenまではやっている.

次.

int pad = 4 – (charlen & 3);

は,結論から書くと4の余り算をしている.
2のn乗の余り算は,number & ((2のn乗) – 1)でできる.

2のn乗 – 1は2進で見るとn個1が並ぶ.


2 ^ 2 – 1 = 3 (11
2 ^ 3 – 1 = 7 (111)
2 ^ 4 – 1 = 15 (1111)

なので,その桁以上は絶対0になる = 余り算になる.

3で&すると4の余り算になる例.
sc_32_2.png

余り算になっているのは,もし,strが1文字だったら余り3で
dst[0]の残りのビットを埋めて
dst[1]は何もやらないためか.

で,0を代入するので,それぞれ8bitずつ0になっていく

こんなコードを書いてDebuggerで追うと

    UInt32 *dst;

    UInt32 num = (1 << 32) – 1;

    dst = &num;

    

    char *cdst0 = (char*)(dst);

    *cdst0++ = 0;

    *cdst0++ = 0;

    *cdst0++ = 0;

    *cdst0++ = 0;

11111111111111111111111111111111

11111111111111111111111100000000

11111111111111110000000000000000

11111111000000000000000000000000

00000000000000000000000000000000

こうなっていくのが観察できる.(PPCだと逆)

たとえば,”abcdefg”
だと,7文字なのでdst[1]の31~24番目のbitは0になる.

‘a’ 01100001

‘b’ 01100010

‘c’ 01100011

‘d’ 01100100

‘e’ 01100101

‘f’ 01100110

‘g’ 01100111

なので

1100100 01100011 01100010 01100001

01100111 01100110 01100101

になるはず.

sc_32_1.png

実際そうなる

で,このstr4cpyの結果はHashのキーとして使われるのであります.

このエントリーをはてなブックマークに追加

031. SC_Lockで排他制御 mutexの初歩

Posted in SuperCollider Code Reading on 4月 4th, 2008 by Norihisa Nagano – Be the first to comment

SCCR 31回目.

http://www.linux.or.jp/JM/html/glibc-linuxthreads/man3/pthread_mutex_lock.3.html

mutex は、排他制御 (MUTual EXclusion) の仕組みであり、共有データの同時更新 からの保護、クリティカルセクション (critical section) や モニタの実装などに使われる。

らしいけど,なんのことやら分かりません.
図書館にあったUNIXネットワークプログラミングにこういう例が載っている

#include <iostream>

#include <pthread.h>

using namespace std;

#define NLOOP 500

int counter = 0;

void *doit(void *vptr){

    for(int i = 0; i < NLOOP; i++){

        int val = counter;

        cout << counter << endl;

        counter = val + 1;

    }

}

int main (int argc, const char * argv[]) {

    pthread_t tidA, tidB;

    pthread_create(&tidA, NULL,&doit, NULL);

    pthread_create(&tidB, NULL,&doit, NULL);

    

    pthread_join(tidA,NULL);

    pthread_join(tidB,NULL);

        

    return 0;

}

doitというのはcounterをインクリメントしていくだけの関数.
なので,これは普通に考えると500 * 2 = 1000(実際は0~999)になりそうですが,そうなりません!

一回,counterの値を取得して,valに代入しているのですが
その時にもう一方のdoitがcounterを書き換えちゃうから.
実際

void *doit(void *vptr){

    for(int i = 0; i < NLOOP; i++){

        cout << counter++ << endl;

    }

}


だと,999にちゃんとなる.

ということで,同時にスレッドが走っているんだけど,処理は片方ずつやってもらわないと困るというときにmutexが登場します.

こう書くとちゃんとカウントしてくれる.

#include <iostream>

#include <pthread.h>

using namespace std;

#define NLOOP 500

int counter = 0;

//こいつを追加

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *doit(void *vptr){

    for(int i = 0; i < NLOOP; i++){        

        //lock

        pthread_mutex_lock(&mutex);

        

        int val = counter;

        cout << counter << endl;

        counter = val + 1;

        

        //unlock

        pthread_mutex_unlock(&mutex);

    }

}

int main (int argc, const char * argv[]) {

    pthread_t tidA, tidB;

    pthread_create(&tidA, NULL,&doit, NULL);

    pthread_create(&tidB, NULL,&doit, NULL);

    

    pthread_join(tidA,NULL);

    pthread_join(tidB,NULL);

    

    return 0;

}


おお,すげぇ.3行増やしただけで!

この処理を,共有変数を相互排除変数で保護した
というらしい.

mutexがだいたい分かったところでSC_Lockを見ると

SC_Lock.h: 27

class SC_Lock

{

public:

    SC_Lock() { pthread_mutex_init (&mutex, NULL); }

    ~SC_Lock() { pthread_mutex_destroy (&mutex); }

    void Lock() { pthread_mutex_lock (&mutex); }

    void Unlock() { pthread_mutex_unlock (&mutex); }

private:

    pthread_mutex_t mutex;

};


おお,initとdestroyとlock/unlockしか使ってない.簡単.
スレッドで共有変数が関係あるときはLockして,処理 -> UnlockするだけでOK.

Objective-Cの

@synchronized(obj)

{

}

も意味的には同じこと(のはず).

スレッド実行の同期

Objective-C では、アプリケーションのマルチスレッド処理をサポートしています。これは、2 つのスレッドが同時に同じオブジェクトを変更しようとする可能性があること、プログラムに深刻な問題を引き起こす可能性がある状況を意味します。同時に複数のスレッドによって実行されないようにコードの一部を保護できるように、Objective-C では @synchronized() ディレクティブを提供しています。

@synchronized() ディレクティブは、コードの一部を 1 つのスレッドで使用するようにロックします。スレッドが保護コードを抜け出すまで、つまり、@synchronized() ブロックの最後の文を通過するまで、他のスレッドはブロックされます。

@synchronized() ディレクティブは、唯一の引数として self を含む任意の Objective-C オブジェクトを受け取ります。このオブジェクトは、相互排他(mutual exclusion)セマフォまたはミューテックス(mutex)と呼ばれています。これにより、スレッドはコードの一部を他のスレッドに使用されないようにロックできます。プログラムの個別のクリティカルセクションを保護するには、別々のセマフォを使用する必要があります。アプリケーションのマルチスレッド処理を開始する前に、すべての相互排他オブジェクトを作成して、競合状態を回避するのが最も安全です。

やっとsocket/thread/mutexの基本を押さえたので次はSCのコードで実装を追っていく.

このエントリーをはてなブックマークに追加

030. pthreadの初歩とthreadのpriorityを実験

Posted in SuperCollider Code Reading on 4月 3rd, 2008 by Norihisa Nagano – Be the first to comment

SCCRもう30回目.

まだまだSC_UdpInPortの続き.

SC_ComPort.cpp: 388

void* SC_UdpInPort::Run()

{

    OSC_Packet *packet = 0;

    while (true) {        

        if (!packet) {

            // preallocate packet before we need it.

            packet = (OSC_Packet*)malloc(sizeof(OSC_Packet));

        }

        

        packet->mReplyAddr.mSockAddrLen = sizeof(sockaddr_in);

        int size = recvfrom(mSocket, mReadBuf, kMaxUDPSize , 0,

                                (struct sockaddr *) &packet->mReplyAddr.mSockAddr, (socklen_t*)&packet->mReplyAddr.mSockAddrLen);

        

        if (size > 0) {

            char *data = (char*)malloc(size);

            memcpy(data, mReadBuf, size);

            if (mWorld->mDumpOSC) dumpOSC(mWorld->mDumpOSC, size, data);

            

            packet->mReplyAddr.mReplyFunc = udp_reply_func;

            packet->mSize = size;

            packet->mData = data;

            packet->mReplyAddr.mSocket = mSocket;

            if (!ProcessOSCPacket(mWorld, packet))

            {

                scprintf(“command FIFO full\n”);

                free(data);

                free(packet);

            }

            packet = 0;

            data = 0;

        }

    }

    return 0;

}


OSC_Packetから見ていく.

struct OSC_Packet

{

    char *mData;

    int32 mSize;

    bool mIsBundle;

    ReplyAddress mReplyAddr;

};

struct ReplyAddress

{

    struct sockaddr_in mSockAddr;

    int mSockAddrLen;

    int mSocket;

    ReplyFunc mReplyFunc;    

};

typedef void (*ReplyFunc)(struct ReplyAddress *inReplyAddr, char* inBuf, int inSize);

ぐらいか.

特に難しいもの無し.

OSC_Packetに受信したデータと,送信元のアドレスを保存.
あとはudp_reply_funcがお返し用関数(のポインタ).

SC_ComPort.cpp: 369

void udp_reply_func(struct ReplyAddress *addr, char* msg, int size)

{

    int total = sendallto(addr->mSocket, msg, size, (sockaddr*)&addr->mSockAddr, addr->mSockAddrLen);

    if (total < size) DumpReplyAddress(addr);

}

sendalltoを呼んでいる.

SC_ComPort.cpp: 614

int sendallto(int socket, const void *msg, size_t len, struct sockaddr *toaddr, int addrlen)

{

    int total = 0;

    while (total < (int)len)

    {

        int numbytes = sendto(socket, msg, len – total, 0, toaddr, addrlen);

        if (numbytes < 0) {

            scprintf(“sendallto errno %d %s\n”, errno, strerror(errno));

            return total;

        }

        total += numbytes;

        msg = (void*)((char*)msg + numbytes);

    }

    return total;

}

sendalltoは,msgがsendtoで全部送れるまでloopで回しているだけ.

ということで,受信したデータの処理が終わったら,udp_reply_funcを呼んでお返ししている様子.

肝はProcessOSCPacketである.

bool ProcessOSCPacket(World *inWorld, OSC_Packet *inPacket)

{

    scprintf(“ProcessOSCPacket %d, ‘%s’\n”, inPacket->mSize, inPacket->mData);

    if (!inPacket) return false;

    bool result;    

    inWorld->mDriverLock->Lock();

        SC_AudioDriver *driver = AudioDriver(inWorld);

        if (!driver) return false;

        inPacket->mIsBundle = gIsBundle.checkIsBundle((int32*)inPacket->mData);

        FifoMsg fifoMsg;

        fifoMsg.Set(inWorld, Perform_ToEngine_Msg, FreeOSCPacket, (void*)inPacket);

        result = driver->SendOscPacketMsgToEngine(fifoMsg);

    inWorld->mDriverLock->Unlock();

    return result;

}

mDriverLockはSC_Lockで,Lock()は

SC_Lock.h: 32

void Lock() { pthread_mutex_lock (&mutex); }

はい,またまた出てきました,pthread.
ということで,やはり先にThreadを片づけねばなりません.

NSThreadに頼りっぱなしでpthread_***は使ったことないのである.

そもそもスレッドを使うのはなぜか.
例えば,前々回のrecvfromを使ったコードは,while(1)のループに入ると,他は何もできません.
そこで,whileループを別の流れで処理させておいて,他の仕事をできるようにする必要があって,そのときには
whileループ + 他の仕事
2個の流れがある.このとき,スレッドが2個あるのでマルチスレッドと言う.
だもんで,socketやるときはthreadが関係してくるのが分かる.

で,2個スレッドがあるということは,場合にもよるが,2個の間でデータが同期していないとまずかったりします.
なのでスレッドにはスレッドを作る,同期する仕組みがあるが,これがかなり難しい.

SCに限って言うと,SCがやりたいのは「いつ飛んでくるか分からないOSCを適切に処理したい」である.
この視点でスレッドを攻略していく.

まずはthreadを作る

int       pthread_create(pthread_t * __restrict,

                         const pthread_attr_t * __restrict,

                         void *(*)(void *),

                         void * __restrict);

pthread_t型はスレッドID.このIDでスレッドを識別する.
昔の本を読むと,pthread_tはlongの場合が多いと書いてあるが

OSXでは構造体なので注意.

typedef __darwin_pthread_t        pthread_t;

typedef struct _opaque_pthread_t *__darwin_pthread_t;

pthread_attr_tは属性を設定する.NULLだとdefault.
次のvoid *(*)(void *)はvoidポインタを返す関数のポインタを渡す.
threadが作られると,この関数が呼ばれる.
返値は,pthread_joinの最後の引数に渡される.
スレッドの終了ステータスとして使うらしい.いろんなコードを見ていると,0を返す場合が多いみたい.

pthread_join

int       pthread_join(pthread_t , void **)

pthread_tのスレッドの終了を待つ.

pthread_self
pthread_createで作った関数内で呼ぶとスレッドのID(pthread_t)が取得できる.

pthread_exit
スレッドを終了する.

これぐらいで何か書けそうなので書いてみる.

#include <iostream>

#include <pthread.h>

using namespace std;

void * thread_function(void *arg) {

    cout << pthread_self() << endl;

    

    int count = 5;

    while(count){

        usleep(1000000);

        printf(” while %d\n”,count);

        count–;

    }

    

    pthread_exit( NULL );

}

int main (int argc, const char * argv[]) {    

    pthread_t pt;

    pthread_create( &pt, NULL, &thread_function, (void*)0);

    cout << “_pthread_create” << endl;

    

    return 0;

}

はい,何も実行されません.環境によっては一回ぐらいthread_functionが呼ばれることもあるんだろうか.

int main (int argc, const char * argv[]) {    

    pthread_t pt;

    

    pthread_create( &pt, NULL, &thread_function, (void*)0);

    

    cout << “pthread_join” << endl;

    pthread_join(pt, (void **)0);

    cout << “_pthread_join” << endl;

    

    return 0;

}

joinするとこうなる.

pthread_join

 while 5

 while 4

 while 3

 while 2

 while 1

_pthread_join

これはthread_functionが終わるまで,待って次の処理に移っています.

さっきの例は,thread_functionが実行されるまえにmain関数が終わってしまっていたわけです.

じゃ,mainが終わらないように,次はこうやってみよう.

#include <iostream>

#include <pthread.h>

using namespace std;

void * thread_function(void *arg) {    

    int count = 5;

    while(count){

        sleep(1);

        cout << ” while “ << count << endl;

        count–;

    }

    

    pthread_exit( NULL );

}

int main (int argc, const char * argv[]) {    

    pthread_t pt;

    

    pthread_create( &pt, NULL, &thread_function, (void*)0);

    cout << “_pthread_create” << endl;

    

    sleep(8);

    

    return 0;

}

_pthread_create

 while 5

 while 4

 while 3

 while 2

 while 1


こうやると,thread_functionは別で勝手に動いていて処理は次に移っている.(だから_pthread_createがprintされる)

これぐらい押さえたところで,SCに戻る.

SC_ComPort.cpp: 307

Start();
が呼ばれている.

親のSC_CmdPort::Start()が呼ばれる.

SC_ComPort.cpp: 255

void SC_CmdPort::Start()

{

    pthread_create (&mThread, NULL, com_thread_func, (void*)this);

    set_real_time_priority(mThread);

}

でた,pthread_create.
引数にthisである.

呼ばれる関数はこれ.

void* com_thread_func(void* arg)

{

    SC_CmdPort *thread = (SC_CmdPort*)arg;

    void* result = thread->Run();

    return result;

}

thisを(void*)で渡しているので,argはSC_CmdPort.Run()を実行.

void* SC_UdpInPort::Run()

{

    OSC_Packet *packet = 0;

    while (true) {

なので,ずっとループ.
で,joinだとかはやっていないので,処理は次に進む.

SC_ComPort.cpp: 258

set_real_time_priority(mThread);

#ifdef SC_LINUX

とかはざくざく消しちゃうことにしました.

void set_real_time_priority(pthread_t thread)

{

    int policy;

    struct sched_param param;

    pthread_getschedparam (thread, &policy, &param);

    policy = SCHED_RR;         // round-robin, AKA real-time scheduling

    param.sched_priority = 63; // you’ll have to play with this to see what it does

    pthread_setschedparam (thread, policy, &param);

}

policyとparamを再セットしている.
http://www.linux.or.jp/JM/html/glibc-linuxthreads/man3/pthread_setschedparam.3.html

pthread_setschedparam はスレッド target_thread のスケジューリングパラメータを policy と param で示される値に変更する。 policy は SCHED_OTHER ( 通常の、リアルタイムでないスケジューリング ) 、 SCHED_RR ( ラウンドロビン方式のリアルタイムスケジューリング ) 、 SCHED_FIFO ( 先入れ先出し (FIFO) 方式のリアルタイムスケジューリング ) のいずれかの値をとる。 param は 2 つのリアルタイムポリシーに対する スケジューリング優先度を表す。 スケジューリングポリシーに関するさらなる情報は sched_setpolicy(2) を参照のこと。

SCHED_OTHER/SCHED_RR/SCHED_FIFO
のうち,SCHED_RRを使っています.コメントにもそう書いてある.
しかし,こいつはなんなんだ?
http://www.linux.or.jp/JM/html/LDP_man-pages/man2/sched_setscheduler.2.html

異なるのはそれぞれのプロセスは最大時間単位までしか実行できない ということである。SCHED_RR プロセスが時間単位と同じかそれより 長い時間実行されると、その優先度のリストの最後に置かれる。

ラウンドロビン

一つの資源を順番に利用する手法。

らしい.

sched_priorityは

CHED_OTHER でスケジューリングされているプロセスは静的優先度 として 0 が指定されなければならず、SCHED_FIFO や SCHED_RR でスケジューリングされているプロセスは 1 から 99 の範囲の 静的優先度を取ることができる。

のスケジューリングの値.
99が一番優先度が高いのか?
書いてないので分からない.

どういうコードを書けば優先度のテストができるのかもよくわからん.
が,こういうコードを考えてみた.



#include <iostream>

#include <pthread.h>

using namespace std;

int countA = 0;

int countB = 0;

pthread_t tidA, tidB;

void *doit(void *vptr){    

    while(1){

        if(pthread_equal(tidA, pthread_self())){

            countA++;

        }else{

            countB++;

        }        

        if(countA + countB > 100000000){

            break;

        }

    }

    return 0;

}

void set_priority(pthread_t thread, int priority){

    int policy;

    struct sched_param param;

    

    pthread_getschedparam (thread, &policy, &param);

    policy = SCHED_RR;         // round-robin, AKA real-time scheduling

    param.sched_priority = priority; // you’ll have to play with this to see what it does

    pthread_setschedparam (thread, policy, &param);

}

int main (int argc, const char * argv[]) {

    pthread_create(&tidA, NULL,&doit, NULL);    

    set_priority(tidA, 99);

    

    pthread_create(&tidB, NULL,&doit, NULL);

    set_priority(tidB, 1);

    

    pthread_join(tidA,NULL);

    pthread_join(tidB,NULL);

    

    cout << “A : B “ << countA << ” : “ << countB << endl;

    

    return 0;

}

Aが99,Bが1の場合

A : B 99595551 : 404450

逆だと

A : B 435625 : 99564376

おおお,かなり差が出ました.
これはすごい.
63ということは結構高い優先度ですが,最高でもないという微妙なところ.
他に優先する処理のための余りなんでしょう.

で,これは実行するたびに回数は変わるので,あくまで優先度であって,絶対的な何かを保証するものではないことに注意.

さて,SCに戻ると,while(true)内では,
recvfromが何かしら受信するまで待っている.
messageが来ると,ProcessOSCPacketが呼ばれる.

で,最初のinWorld->mDriverLock->Lock();
で,pthread_mutex**に遭遇.

次はmutexをやる.

このエントリーをはてなブックマークに追加

029. C++の例外をさらっと

Posted in SuperCollider Code Reading on 4月 1st, 2008 by Norihisa Nagano – Be the first to comment

送受信をさらっとやったので,前回までにすっとばした例外をやっておく.

図書館にC++プログラミングというぶっとい本があったので試しにこいつを参考にしていく.

C++の例外の構文は

#include <iostream>

#include <exception>

using namespace std;

int main (int argc, char * const argv[]) {

    try{

        throw exception();

    }catch(exception e){

        cout << “catch” << endl;

    }    

    return 0;

}

という感じで,try -> throw -> catchになっている.
どの言語もだいたいこんな感じである.

throwでは何でも投げれるので

    try{

        throw 100;

    }catch(int e){

        cout << e << endl;

    }

とやると,100となる.

で,C++が投げる全ての例外はexception Classを継承しているそうで
階層構造は
http://www.geocities.jp/ky_webid/cpp/library/027.html
こんな感じらしい.

さて,例外はそもそも何のために使うのか.

C++プログラミングの長い文を要約すると

エラー処理のコードをプログラムコードのところどころに書くと,エラーの生じたところで処理できて判断しやすいが,その反面,アプリケーションのコードが「汚染」されてしまう.
だもんで,エラー処理のコードを分離したいので例外を使う

ということになる.

Something *something = new Something();

something->hoge();

something->moge();

….

というコードがあったとして,hogeもmogeもエラーが発生するやもしれない.
hogeとmogeのなかでごちゃごちゃエラー処理するんじゃなくて,

try{

    Something *something = new Something();

    something->hoge();

    something->moge();

    ….

}catch(exception e){

    エラー発生したんで処理します(e)!

}

としておけば,エラー処理を分離できる!

という風に解釈しておく.
たしかに分離できてコード見やすいし,エラー処理部分をまとめられるのでナイス.

では,どういう時に例外処理を使うべきなのか.
作法が書いてあるので引用.

  1. 起きた場所とは異なるスコープで処理しなければならないエラーに対して例外を使ってください.起きた場所と同じスコープ内で処理しうるエラーには,他のエラー処理方法を使うこと
  2. プログラムがわかりにくくなるのを避けるため,エラー処理以外の目的で例外処理を使わないこと

なるほど.

そもそも問題のコードは
SC_ComPort.cpp: 287

if ((mSocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {

    throw std::runtime_error(“failed to create udp socket\n”);

}


である.
これは,socketが失敗したときは-1が返ってくるのでそれを例外として処理している.

まず,std::runtime_errorとは何か

stdexcept: 109

  class runtime_error : public exception 

  {

    string _M_msg;

  public:

    /** Takes a character string describing the error.  */

    explicit 

    runtime_error(const string&  __arg);

    virtual 

    ~runtime_error() throw();

    /** Returns a C-style character string describing the general cause of

     *  the current error (the same string passed to the ctor).  */

    virtual const char

    what() const throw();

  };

となっている.ふむ.

what()で,引数のmessageが取得できるらしい.

socketが失敗する(であろう)コードを書いてみる.

アホなコードなので実行しないほうがいいかもです.

#include <iostream>

#include <exception>

#include <sys/socket.h>

#include <stdexcept>

using namespace std;

int main (int argc, char * const argv[]) {

    int count = 0;

    try{

        int mSocket;

        while(1){

            if ((mSocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {

                throw std::runtime_error(“failed to create udp socket\n”);

            }

            count++;

        }

    }catch(exception &e){

        cout << e.what() << endl;

        cout << “count = “ << count << endl;

    }

    return 0;

}

僕の環境では,2558回目でsocketに失敗する.

failed to create udp socket

count = 2557

となる.
こんなにsocketが必要なことも無いだろうが,ともあれ失敗することもあることが判明.

で,scsynthの場合,このsocketが失敗すると,OSCが受け取れずプログラムとしては「致命傷」なわけで
ここでexit()してもいいぐらいだと思うのですが,ここでexit()しちゃうとそこで終り.
もう一度socket()を実行してみたくなったときや,エラーに応じてユーザーにメッセージを出すなんてことができません.

なので,ここでの処理としては
「エラー発生しましたよとお知らせする」
ことにしているのだと思われる.

ということは,必ずこの前の段階でtryがあり,その後にはcatchがあるはず.
コードを追ってみると

SC_World.cpp: 673

void World_OpenUDP(struct World *inWorld, int inPort)

{

    try {

        new SC_UdpInPort(inWorld, inPort);

    } catch (std::exception& exc) {

        scprintf(“Exception in World_OpenUDP: %s\n”, exc.what());

    } catch (…) {

    }

}


でcatchされておりました.

こういう風に書き換えてみると分かりやすい.
SC_ComPort.cpp: 284

SC_UdpInPort::SC_UdpInPort(struct World *inWorld, int inPortNum)

    : SC_ComPort(inWorld, inPortNum)

{    

    //必ず例外を投げる

    throw std::runtime_error(“failed to create udp socket\n”);

    printf(“____below”);

で,throwされた場合,その後のコードは実行されないので,goto的な役割もあるわけですね.
socket()がエラーなのに,bind()等を実行しても無駄ということ.
(____belowはprintされない)

しかし,「致命傷」と思いきや,例外が発生しても

Exception in World_OpenUDP: failed to create udp socket

SuperCollider 3 server ready..


と出た.:)
これぐらいの例外じゃ終了はしないらしい.

catchだけ見るとこれだけしかないし,これぐらいやっとけば大丈夫でしょう.
sc_29.png

このエントリーをはてなブックマークに追加

028. SC_UdpInPortのSocket処理(2) recvfrom

Posted in SuperCollider Code Reading on 3月 31st, 2008 by Norihisa Nagano – Be the first to comment

今回は受信.
UDPで受信するとき,というよりは,SOCK_DGRAMでopenしたsocketの受信にはrecvformを使う.

socket -> bind -> recvfrom -> close
という手順になる.


SC_ComPort.cpp: 303

if (bind(mSocket, (struct sockaddr *)&mBindSockAddr, sizeof(mBindSockAddr)) < 0) {

bind
http://www.linux.or.jp/JM/html/LDP_man-pages/man2/bind.2.html

socket(2) でソケットが作成されたとき、そのソケットは名前空間 (アドレス・ファミリー) に 存在するが、アドレスは割り当てられていない。 bind() は、ファイルディスクリプタ sockfd で参照されるソケットに addr で指定されたアドレスを割り当てる。 addrlen には addr が指すアドレス構造体のサイズをバイト単位で指定する。 伝統的にこの操作は 「ソケットに名前をつける」 と呼ばれる。

らしい.
お決まりの手順で覚えていいと思う.
前回までで使った変数しか使わないので問題無し.

で,その後は別ThreadでRunが呼ばれるのですが,Threadはすっとばして(後でやる)recvfromを見ておく.
SC_ComPort.cpp: 380

void* SC_UdpInPort::Run()

{

    OSC_Packet *packet = 0;

    while (true) {        

        if (!packet) {

            // preallocate packet before we need it.

            packet = (OSC_Packet*)malloc(sizeof(OSC_Packet));

        }

        

        packet->mReplyAddr.mSockAddrLen = sizeof(sockaddr_in);

        int size = recvfrom(mSocket, mReadBuf, kMaxUDPSize , 0,

                            (struct sockaddr *) &packet->mReplyAddr.mSockAddr, (socklen_t*)&packet->mReplyAddr.mSockAddrLen);

        

        if (size > 0) {

            char *data = (char*)malloc(size);

            memcpy(data, mReadBuf, size);

            if (mWorld->mDumpOSC) dumpOSC(mWorld->mDumpOSC, size, data);

            

            packet->mReplyAddr.mReplyFunc = udp_reply_func;

            packet->mSize = size;

            packet->mData = data;

            packet->mReplyAddr.mSocket = mSocket;

            if (!ProcessOSCPacket(mWorld, packet))

            {

                scprintf(“command FIFO full\n”);

                free(data);

                free(packet);

            }

            packet = 0;

            data = 0;

        }

    }

    return 0;

}


他がややこしいので,先にrecvfromだけ見る.

recvfrom
http://www.linux.or.jp/JM/html/LDP_man-pages/man2/recv.2.html

ssize_t recvfrom(int s, void *buf, size_t len, int flags,

                 struct sockaddr *from, socklen_t *fromlen);

from が NULL 以外で、下層のプロトコルから送信元アドレスが分かる場合、 from にはこの送信元アドレスが入れられる。 引き数 fromlen は入出力両用のパラメータで、呼び出し時には from に割り当てたバッファの大きさを入れておき、返ってくる時には実際に from に格納されたアドレスの大きさに変更される。

らしい.

from にはこの送信元アドレスが入れられる

おお,便利.
これで,送ってきた人に返事が返せることが分かる.

他の引数は特に難しいことはなし.
ということで,またもやSC_UdpInPortを参考に,受信するコードを書いてみよう.


#import <Foundation/Foundation.h>

#include <sys/socket.h>

#include <netinet/in.h>

const size_t kMaxUDPSize = 65535;

int main (int argc, const char * argv[]) {

    int mPortNum;

    int mSocket;

    struct sockaddr_in mBindSockAddr;

    

    mPortNum = 1234;

    mSocket = socket(AF_INET, SOCK_DGRAM, 0);

    

    bzero((char *)&mBindSockAddr, sizeof(mBindSockAddr));

    mBindSockAddr.sin_family = AF_INET;

    mBindSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    mBindSockAddr.sin_port = htons(mPortNum);

    

    ////ここまで前回と同じ

    bind(mSocket, (struct sockaddr *)&mBindSockAddr, sizeof(mBindSockAddr));

    

    struct sockaddr receiveSockAddr;

    unsigned char mReadBuf[kMaxUDPSize];

    socklen_t addrsize;

    while(1){

        int size = recvfrom(mSocket, mReadBuf, kMaxUDPSize , 0,

                            &receiveSockAddr, &addrsize);

        if (size > 0) {            

            printf(“size = %d , mReadBuf = %s\n”,size, mReadBuf);

            break;

        }

    }

    close(mSocket);

    return 0;

}

で,このコードを実行して,その後で前回のコードを実行すると

size = 5 , mReadBuf = hoge

と出るはず.
簡単ですね.

SC_UdpInPortもやってることの本質は同じ.
次はOSC_PacketとかThreadを解明していく.これまですっとばしたエラーが返ってきたときの対処(C++の例外)も補足する.

このエントリーをはてなブックマークに追加

027. SC_UdpInPortのSocket処理(1) sendto

Posted in SuperCollider Code Reading on 3月 30th, 2008 by Norihisa Nagano – Be the first to comment

前回のsendOSC編でひがくんからOSCのライブラリはこれがいいよと教えてもらいました.
http://liblo.sourceforge.net/

しかも,VP3LのBinary出てました.うお.

SCCR27回目.さて,Socket編.

24回目でsocketのAPIに遭遇したのは

SC_ComPort.cpp: 284

SC_UdpInPort::SC_UdpInPort(struct World *inWorld, int inPortNum)

    : SC_ComPort(inWorld, inPortNum)

{

    if ((mSocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {

        throw std::runtime_error(“failed to create udp socket\n”);

    }

    {

        int bufsize = 65536;

        setsockopt(mSocket, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));

    }

    

    //scprintf(“@@@ sizeof(ReplyAddress) %d\n”, sizeof(ReplyAddress));

    bzero((char *)&mBindSockAddr, sizeof(mBindSockAddr));

    mBindSockAddr.sin_family = AF_INET;

    mBindSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    mBindSockAddr.sin_port = htons(mPortNum);

    if (bind(mSocket, (struct sockaddr *)&mBindSockAddr, sizeof(mBindSockAddr)) < 0) {

        throw std::runtime_error(“unable to bind udp socket\n”);

    }

    Start();

#ifdef USE_RENDEZVOUS

    if(inWorld->mRendezvous){

        pthread_create(&mRendezvousThread, 

            NULL

            rendezvous_thread_func, 

            (void*)this);

    }

#endif

}


でした.
で,これはSC_Udp”In”Portなので受信ですね.
送る方を見るには,Languageを見なけりゃなりませんが,LanguageのOSC送信にはInterpreterがからんでくるのでぶっとばして,scsynthのOSC(socket)部分をやります.

まず,TCPとUDPは何が違うのか
http://www.geekpage.jp/technology/ip-base/tcp-udp.php

TCPとUDPはIPの上の層に存在するプロトコルです。 IP層は目的地(宛先)ホストまでパケットを運ぶ役目を負っています。 しかし、実際に通信をするにはそれでは不十分です。 TCPやUDPはその不十分なところを補完するものです。
TCPとUDPには、それぞれ特徴があります。 TCPはデータを正しく全部届けたい場合に有効で、UDPは途中でデータが無くなってでも早く届けたい場合に有効です。 TCPは1対1の通信しかできませんが、UDPは一度に大量の受信者宛にデータを送信することができます。
インターネット上を流れるトラフィックの9割以上はTCPによるものであると言われています。 例えば、WWW、メール、FTPなどは全部TCPです。 「インターネット」という単語から思いつく通信の多くがTCPを使っていると言っても過言ではないかも知れません。
一方、音声通話、映像配信などではUDPが多く利用されています。 例えば、音声通話では時間がかかってでも正しく音が全部伝わることよりも、多少途切れてでもすぐに相手に伝わる事の方が重要な場合が多いです。 また、UDPでは一度に複数の相手にデータを遅れるため(マルチキャストやブロードキャスト)、放送型の映像配信や音声配信に利用されます。

明快です.

そもそもソケット通信とはなんですか
http://www.ne.jp/asahi/hishidama/home/tech/socket/index.html

を参考にしてみると,送るには
socket -> sendto -> close

受信は
socket -> bind -> recvfrom -> close

という手順を踏む必要があることが分かる.

まずはこれから

mSocket = socket(AF_INET, SOCK_DGRAM, 0)

http://www.linux.or.jp/JM/html/LDP_man-pages/man2/socket.2.html
で,socket()を見てみると

AF_INET
AF_INETがIPv4. AF_INET6がIPv6らしい.

SOCK_DGRAM
データグラム(接続、信頼性無し、固定最大長メッセージ) をサポートする。

を使っている.
UDPは信頼性よりも速度優先.一発処理して届くかはしらないプロトコル.

SOCK_DGRAMを使う場合,受信にはrecvformを使うらしい.

成功した場合、新しいソケットのファイル・ディスクリプターを返す。 エラーが発生した場合は -1 を返し、 errno を適切に設定する。  

なので,mSocketがディスクリプターというらしい.こいつを中心にsocket関数を使っていくことになる.

次にoptionで,bufferサイズを変更している.
setsockopt
http://www.linux.or.jp/JM/html/LDP_man-pages/man2/setsockopt.2.html

socket.h: 206

#define    SOL_SOCKET    0xffff        /* options for socket level */


socket.h: 168

#define SO_SNDBUF    0×1001        /* send buffer size */

int bufsize = 65536;

setsockopt(mSocket, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));

なので65536 byte.

次.
sockaddr_in構造体.
こいつはIPアドレスとポート番号の管理用.

netinet/in.h: 362

/*

 * Socket address, internet style.

 */

struct sockaddr_in {

    __uint8_t    sin_len;

    sa_family_t    sin_family;

    in_port_t    sin_port;

    struct    in_addr sin_addr;

    char        sin_zero[8];        /* XXX bwg2001-004 */

};


netinet/in.h: 304

/*

 * Internet address (a structure for historical reasons)

 */

struct in_addr {

    in_addr_t s_addr;

};


SC_ComPort.cpp: 298

bzero((char *)&mBindSockAddr, sizeof(mBindSockAddr));

mBindSockAddr.sin_family = AF_INET;

mBindSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);

mBindSockAddr.sin_port = htons(mPortNum);

bzeroは0で埋める.これをやらないと稀にBUGります,
Cの掟 : 構造体は必ず初期化せよ!!

AF_INETはさっきのやつ.
INADDR_ANYはこのマシンのどのネットワーク・インターフェイスも使いますよの意らしい.

http://www.linux.or.jp/JM/html/LDP_man-pages/man3/byteorder.3.html

htonl() 関数は unsigned integer hostlong を ホストバイトオーダーからネットワークバイトオーダーに変換する。
らしい.
sys/types.h: 126

typedef    __uint32_t        in_addr_t;    /* base type for internet address */

htons() 関数は unsigned short integer hostshort を ホストバイトオーダーからネットワークバイトオーダーに変換する。
同じことですな.(型が違う)

あら,ここまででsendtoに必要なものが揃ってしまった.
先にsendtoを試してみよう.

http://www.linux.or.jp/JM/html/LDP_man-pages/man2/send.2.html

ssize_t sendto(int s, const void *buf, size_t len, int flags,

               const struct sockaddr *to, socklen_t tolen);

最初のint sはディスクリプター(mSocket).
bufは送るデータ.
size_t lenはデータの長さ.
sockaddr*はさっきのsockaddr_in.
おや,sockaddrとsockaddr_inがある!とびっくりですが,
sockaddr-> sockaddr_inに拡張されたらしい.

分からないのは,flagsぐらいか.
flagsは通常0.何かやるときは↑のページにフラグの機能が書いてある.

ということで,SC_UdpInPortを参考に,sendtoを実行するコードを書く.

#import <Foundation/Foundation.h>

#include <sys/socket.h>

#include <netinet/in.h>

int main (int argc, const char * argv[]) {

    int mPortNum;

    int mSocket;

    struct sockaddr_in mBindSockAddr;

    

    mPortNum = 1234;    

    mSocket = socket(AF_INET, SOCK_DGRAM, 0);

    

    bzero((char *)&mBindSockAddr, sizeof(mBindSockAddr));

    mBindSockAddr.sin_family = AF_INET;

    mBindSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    mBindSockAddr.sin_port = htons(mPortNum);

    

    char *msg = “hoge”;

    sendto(mSocket, msg, strlen(msg) + 1, 0, &mBindSockAddr, sizeof(mBindSockAddr));

    

    close(mSocket);

    

    return 0;

}


mPortNumberと同じportでscsynthを起動して
このコードを実行すると・・・

sc_27.png

おっとー,ちゃんと送れています.
hogeというコマンドは存在しないのでFAILUREと出ますが.

ここで,C言語の文字列について.

char *msg = “hoge”;

char msg[] = “hoge”;

char msg[] = {‘h’,‘o’,‘g’,‘e’,‘\0′};

は同じこと(C言語ポインタ完全制覇参照)

しかし

char *msg = “hoge”;

は,“hoge” => char[5]の配列があって,そのポインタがmsgだ
という意味になるので,まったく同じというわけではない.

strlenは‘\0′までをカウントするので,4になるが,送る長さは + 1して5になるのでstrlen + 1とする.
ためしに + 1しないで送るとたまにBugります.

printf(“strlen = %d”,strlen(msg));

とでもやってみるとよい.

送信はできたので,受信を見ていく.

このエントリーをはてなブックマークに追加

026. Open Sound Controlとは何か(sendOSCをbuild)

Posted in SuperCollider Code Reading on 3月 23rd, 2008 by Norihisa Nagano – 4 Comments

昨日はdreamhostが死亡していました.

SCで頻繁に出てくるOpen Sound Control(OSC)とは,そもそも何なのか.
http://opensoundcontrol.org/

ざっくり言うとCNMATが開発したプロトコルです.

OpenSound Control (“OSC”) is a protocol for communication among computers, sound synthesizers, and other multimedia devices that is optimized for modern networking technology and has been used in many application areas.
と書いてある.

http://opensoundcontrol.org/spec-1_0
に仕様が書いてある.かなりシンプル.

http://opensoundcontrol.org/implementations

に実装したSoftware/Hardwareのリストあり.
MIDIは遅いし,Stringが送れない(よな?sysexでできるか?)けど
OSCなら割と高速に送れます.

で,SCはOSCで色々やりとりする仕様になっています.
何か実行するにしても,自分自身にOSC packetを送って実行するようになっている(たぶん.今までcode見た上での予測).

OSCの実装は
http://archive.cnmat.berkeley.edu/OpenSoundControl/Kit/
に参考のソースがある.
というよりも,論文書いたときの最初の実装か.
が,makeできず.

めんどくさいのでXcodeでProjectを作ってBuildしてみる.
まずはFoundation ToolでProjectを作る.

sc26_1.png

別にFoundation Toolじゃなくてもいいけど,Foundation Toolだと,あとで改造したいときに便利.

test***というファイル以外の.hと.cをコピーする.

sc26_4.png

構成はこうなる.

ビルドすると

sc26_2.png

なんかエラー出ますが.

__sgiが定義されてなくて

#include <netinet/in.h>

が無効になっている.
sgiってシリコングラフィックスのSGIですかいね.

#include <netinet/in.h>

struct NetworkReturnAddressStruct {

    struct sockaddr_in cl_addr;

    int clilen;

    int sockfd;

};

にしちゃう,

sc26_3.png

またエラー.
定義が無いのが問題みたいなので,プロトタイプ宣言を追加してやる.
OSC-address-space.c: 89ぐらいに

static int gasHelp(char *target, int maxLength, OSCcontainer c);

と書く.

よし,ビルドできた.

で,ライブラリ部分っぽいところはビルドできたので

http://archive.cnmat.berkeley.edu/OpenSoundControl/clients/sendOSC.html

のsendOSCを動かしてみたい!!

sendOSCという簡単なコードがあるらしいが,CNMATのPageが最近再構築したのか,404続発なので
http://swiki.hfbk-hamburg.de:8888/MusicTechnology/523
から,sendOSC.cを取得.

Projectにcopy.
OSC.mを削除する.

で,ビルドすると
OSC-client.h
がありませーんになるので

http://archive.cnmat.berkeley.edu/OpenSoundControl/Kit/
から,
OSC-client.h
OSC-client.c
全部download.
Projectに追加する
それでもエラーになる.

#include “htmsocket.h”

の,htmsocket.hが無い.
ネットで検索してコードがあったので,自前で作る.

それでもごにょごにょエラーが出るので修正したらビルドできる.

起動時の引数は -h で送るaddressを渡す.
SCのdefault portは57110らしいので57110にする.

sc26_5.png

こうなる.

で,SC(SuperCollider.app)を起動する.

Xcodeでビルドと実行
コンソールで

sc26_6.png

とやると,音が出る.(sineというsynthdefが作成済みとする)

おおお,これは便利かもしれない.Max立ち上げなくていい.
しかし,portの動的変更も出来たら便利だな.
& recieveの確認もできたらいいな.
(素直にSC.appを使えばいいのですが,scsynthをやるときはsclangを切り離してやりたい)

SCCRなので,SCのソースでOSCの実装を読み解きながら,これと同じのを作ってSocketを攻略する.
で,Recieveも実装すると,その時にはThreadが関係してくるのである!

今回作ったProjectはこちら.

Download -> sendOSC.zip

このエントリーをはてなブックマークに追加

025. SuperCollider.app Quick Hack(1) Enterじゃなくて⌘ + Returnで実行したい

Posted in SuperCollider Code Reading on 3月 21st, 2008 by Norihisa Nagano – Be the first to comment

突如Cocoaがやりたくなったので,Languageを見ていく.

Language.xcodeproj
です.

こっちはSuperColliderのApplicationの実装です.

知りたいのは,テキストを選択してenterを押したときに何が起こっているのか?
である.

まずは,SCはDocument Based Applciationなので
MyDocumentがDocumentの実装である.

で,nibファイルが読み込まれた後

- (void)windowControllerDidLoadNib:(NSWindowController*) aController

が呼ばれるので,こいつを見ていく.

- (void)windowControllerDidLoadNib:(NSWindowController*) aController

{

    [super windowControllerDidLoadNib:aController];

    NSSize contentSize;

    contentSize = [scrollView contentSize];

    if (initTextView) {

        textView = initTextView;

    } else {

        textView = [self makeTextView];

    }

    [scrollView setDocumentView: textView];

…..

ということで,

firstWindow

は,postというタイトルのコンソール用だと予測できる.

bool firstWindow = true;

なので,最初はtrue

initTextViewは最初はnilなので

textView = [self makeTextView];

が呼ばれる.

MyDocument.M: 70

- (SCTextView*)makeTextView

{

    SCTextView* aTextView = [[SCTextView alloc] initWithFrame

                    NSMakeRect(0,0,612,512)];

    [aTextView setAutoresizingMask: 63];

    [[aTextView textContainer] setWidthTracksTextView: YES];

    [aTextView setDelegate: self];

    [aTextView setAllowsUndo: YES];

    [aTextView setRichText: YES];

    [aTextView setSmartInsertDeleteEnabled: NO];

    [aTextView setImportsGraphics: YES];

    [aTextView setFont: [NSFont fontWithName: @"Monaco" size: 9]];

    [aTextView setLangClassToCall:@"CocoaDocument" 

            withKeyDownActionIndex:4 withKeyUpActionIndex:5];

    [aTextView setObjectKeyDownActionIndex:2 setObjectKeyUpActionIndex:1];

    [aTextView setAcceptsFirstResponder:YES];

    isRichText = YES;

    return aTextView;

}

ということで,いつもSinOscとか打ってるのはSCTextViewであることが分かる.

ということは当然,enter押して呼ばれているのは
SCTextView.M: 154

- (void) keyDown: (NSEvent*) event

これ.

で,enterで実行してるのは

    if ([characters isEqual: @"\03"]) {

        [[self delegate] executeSelection: self];

    }

これ.

MyDocument.M: 70

[aTextView setDelegate: self];

なので,delegateはMyDocumentです.

MyDocument.M: 587

- (IBAction) executeSelection: (id) sender

{

    [self sendSelection: "interpretPrintCmdLine" ];

}

MyDocument.M: 922

- (void)sendSelection: (char*) methodName

{        

    if (!compiledOK) {

        return;

    }

    NSRange selectedRange;

    NSString* selection = [textView currentlySelectedTextOrLine: &selectedRange];

    const char *text = [selection UTF8String];

    int textlength = strlen(text);

    [[SCVirtualMachine sharedInstance] setCmdLine: text length: textlength];

    

    NSRange newSelectedRange = NSMakeRange(selectedRange.location + selectedRange.length, 0);

    [textView setSelectedRange: newSelectedRange];

    

    pthread_mutex_lock(&gLangMutex);

    runLibrary(getsym(methodName));

    pthread_mutex_unlock(&gLangMutex); 

}

ということで,SCVirtualMachineにテキストを送って

runLibrary(getsym(methodName));

すると動くらしい.

ということは,SC Editorを作るにはTextViewのDelegateにMyDocumentをセットする必要があるということになる.

しかし,これは設計よくないなー.
SCTextViewが他のClassに依存しまくりじゃーないですか.涙

気を取り直してHack.

Quick Hack

僕は家では,Apple Keyboardを使っているので,enterが遠い.果てしなく遠い.
なので⌘ + returnで実行したい.
しかし,⌘ + returnではTextViewのkeydownが呼ばれない.

- (void) keyDown: (NSEvent*) event

{

NSLog(@”%s”,__PRETTY_FUNCTION__);

とでも書いて,キーを叩いてみるといい.

⌘キーは,ショートカットで使われるので別扱いなわけです.
(⌘はたぶんwindowsで文字化けしますが,気にせず書く)

ということで,ショートカットのときに呼ばれるmethodを上書きしてあげればよい.

NSResponderの

- (BOOL)performKeyEquivalent:(NSEvent *)theEvent;

をオーバーライドしてあげる.

SCTextView.Mに

- (BOOL)performKeyEquivalent:(NSEvent *)theEvent{

    if ([theEvent modifierFlags] & NSCommandKeyMask && [[theEvent characters] isEqualToString:@”\r”]) {

        [self keyDown:theEvent];

        return YES;

    }else{

        return [super performKeyEquivalent:theEvent];

    }

}

を追加して

SCTextView.M: 191

keyDownの

if ([characters isEqual: @"\03"]) {

if ([characters isEqual: @"\03"] || ([characters isEqual: @"\r"] && [event modifierFlags] & NSCommandKeyMask)){

に修正.
Enterか⌘ + Returnのときは実行するように変更したわけです.

これは便利だーあー.

SCCR初,役に立ちそうな記事になった!うほほ.

SCのDocument ClassのkeyDownでもこればかりは無理な模様.

このエントリーをはてなブックマークに追加

024. OSCのMessageはどこでどう受信されるのか 序章 ThreadとSocketに遭遇

Posted in SuperCollider Code Reading on 3月 21st, 2008 by Norihisa Nagano – Be the first to comment

いろんなトピックを縦横無尽(というよりは気まぐれ)SCCR24回目.

Hashは論文読んでみたけど結構難しいので,とりあえずぶっ飛ばしてNode周りに戻る.
(Hash Tableがでてきたときにやる)

SC_Node.cpp: 100

// delete a node

void Node_Delete(Node* inNode)

{

    if (inNode->mID == 0return// failed

    if (inNode->mIsGroupGroup_Dtor((Group*)inNode);

    else Graph_Dtor((Group*)inNode);

}

Dtorはdestructorの略で,わりと一般的に使われている様子.

ここでNodeはmIsGroupでGroupか,そうじゃなきゃGraphだよというのが分かる.

まずGraphを見る.
Graphとは何か.

SC ServerのNodeのDocumentを見ると
http://supercollider.sourceforge.net/docs/ServerArchitecture/Node.html

abstract superclass of Synth and Group
とあり.
たぶんここで言うSynthの実装がGraphじゃないか.

int Graph_New(struct World *inWorld, struct GraphDef *inGraphDef, int32 inID, 

            struct sc_msg_iter* args, Graph** outGraph)

にブレークポイントを設定して前のMaxのPatchから/s_new sine 1234
を実行すると,
sc_23_1.png

確かに呼ばれている.

同じく

SC_Group.cpp: 48

void Graph_Dtor(Graph *inGraph)

にブレークポイントを設定して/n_free 1234すると



確かに呼ばれている.

SC_Group.cpp: 51

int Group_New(World *inWorld, int32 inID, Group** outGroup)



にブレークポイントを設定しても呼ばれないので間違いないだろう.

Group_Newを呼んでいるのはどこか

SC_MiscCmds.cpp: 614

SCErr meth_s_new(World *inWorld, int inSize, char *inData, ReplyAddress* /*inReply*/)

こいつが呼んでいる.
ここでしか呼ばれない.

SC_MiscCmds.cpp
をざっと見ると,prefixのmethを除くと
http://supercollider.sourceforge.net/docs/ServerArchitecture/Server-Command-Reference.html
に出てくるコマンドになるMethodがいっぱいです.

なるほどなるほど.

meth_ prefixは

#define NEW_COMMAND(name) NewCommand(#name, cmd_##name, meth_##name)

NEW_COMMANDで使われるようになっている.

SC_MiscCmds.cpp: 1507

void initMiscCommands();

でCommandがもりもり作られている.
Server Commandを追加するときはここでやる.

NewCommandは
SC_Lib.cpp: 118

SCErr NewCommand(const char *inPath, uint32 inCommandNumber, SC_CommandFunc inFunc)

{

    char path[256];

    sprintf(path, “/%s”, inPath);

    SC_LibCmd *cmd = new SC_LibCmd(inFunc);

    cmd->SetName(path);

    gCmdLib->Add(cmd);

    

    // support OSC commands without the leading slash

    SC_LibCmd *cmd2 = new SC_LibCmd(inFunc);

    cmd2->SetName(inPath);

    gCmdLib->Add(cmd2);

    // support integer OSC commands

    gCmdArray[inCommandNumber] = cmd;

    return kSCErr_None;

}


gCmdLibgCmdArrayにCommandの実体が格納されるっぽい.

gCmdLibを使うのはたぶんここ.
SC_CoreAudio.cpp: 208

int PerformOSCMessage(World *inWorld, int inSize, char *inData, ReplyAddress *inReply)



おおおー,SC_CoreAudioに戻ってきました.

で,ここでPerformOSCMessageが呼ばれるまでをDebuggerが表示してくれているので見てみると・・・



おおお,前やったMsgFifoが出てきています.
繋がってきたぞ.

Debuggerを追うと

SC_CoreAudio.cpp: 935

mOscPacketsToEngine.Perform();

MsgFifo.h: 52

mItems[next].Perform();

SC_FifoMsg.h: 48

inline void FifoMsg::Perform()

{

    if (mPerformFunc) (mPerformFunc)(this);

}

と繋がる.

mPerformFuncはPerform_ToEngine_Msgだということだ.

ここ
SC_CoreAudio.cpp: 263

void Perform_ToEngine_Msg(FifoMsg *inMsg)

PerformOSCMessage(world, packet->mSize, packet->mData, &packet->mReplyAddr);

で,inCommandNumber,gCmdArrayのindexは

SC_Lib_Cintf.h: 47

// command numbers:

enum {

    cmd_none = 0,

    cmd_notify = 1,

    cmd_status = 2,

    cmd_quit = 3,

    cmd_cmd = 4,

    cmd_d_recv = 5,

    cmd_d_load = 6,

    cmd_d_loadDir = 7,

    cmd_d_freeAll = 8,

…..

s_newは

cmd_s_new = 9

SC_CoreAudio.cpp: 192

bool ProcessOSCPacket(World *inWorld, OSC_Packet *inPacket)



で,Perform_ToEngine_Msgがセットされている.

ProcessOSCPacketを呼んでいるのは

SC_ComPort.cpp: 388

void* SC_UdpInPort::Run()

SC_ComPort.cpp: 415

if (!ProcessOSCPacket(mWorld, packet))

portがUDPなので,TCPだとSC_UdpInPortじゃなくてSC_TcpInPortだと思われる.

SC_CmdPort -> SC_ComPort -> SC_UdpInPortと継承されている.

Runはどこで呼ばれるかというと

void SC_CmdPort::Start()

{

    pthread_create (&mThread, NULL, com_thread_func, (void*)this);

    set_real_time_priority(mThread);

}

で開始されて

SC_ComPort.cpp: 225

void* com_thread_func(void* arg)

{

    SC_CmdPort *thread = (SC_CmdPort*)arg;

    void* result = thread->Run();

    return result;

}

で実行.

pthread_create登場.スレッドだー.

で,SC_UdpInPortのコンストラクタを見ると

if ((mSocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {

    throw std::runtime_error(“failed to create udp socket\n”);

}

ソケットだー.

というわけで,
次回からスレッドとソケット関係をじっくりやっていく.
pthreadはじめて使うわ.

このエントリーをはてなブックマークに追加

023. Objective-CプログラマーのためのSC言語入門(2) またりさまClassを実装

Posted in SuperCollider Code Reading on 3月 20th, 2008 by Norihisa Nagano – Be the first to comment

SC言語編に突如突入したSCCR23回目.
言語仕様を眺めても言語は理解できない.何か作っていくなかで本当の理解は得られるのです!
ということで
前回,クラスの初歩までやったので
今回はまたりさまクラスをつくることにする.

またりさまとは

またりさまは三輪眞弘教授の逆シミュレーション音楽の第一弾です.

逆シミュレーション音楽
http://www.iamas.ac.jp/~mmiwa/rsm.html

またりさま
http://www.iamas.ac.jp/~mmiwa/XORensemble.html

・8人のプレーヤーが同じ方向に輪になって座る。(全員が次の人の背中を見ている、輪になった電車ごっこ状態)
・全員が両手に楽器、右手に鈴、左手にカスタネットをもつ。
・「鈴カケ」のルールに従って、自分の肩がたたかれたら自分の前に座っている人の肩をたたいて楽器を鳴らす。

「鈴カケ」のルール:
・鈴を鳴らすときは次の人の右肩をたたいて音を出す。
・カスタネットを鳴らすときは次の人の左肩をたたいて音を出す。

それぞれは自分が最後に鳴らした楽器によって毎回「スズ状態」と「カケ状態」(それぞれスズ、カスタネットに対応する)の2種類の状態のどちらかになり、以下の規則に従って、次の番に鳴らすべき楽器を決める。

スズ状態の時、<同じ楽器を鳴らす>
・自分の背中で鈴が鳴らされたら自分も鈴を鳴らす。(スズ状態のまま)
・自分の背中でカスタネットが鳴らされたら自分もカスタネットを鳴らす。(その後、カケ状態になる)

カケ状態の時、<もうひとつの楽器を鳴らす>
・自分の背中で鈴が鳴らされたら自分はカスタネットを鳴らす。(カケ状態のまま)
・自分の背中でカスタネットが鳴らされたら自分は鈴を鳴らす。(その後、スズ状態になる)

これを8人が連続して次々と繰り返し行う。

さて,この状態の変化を数値で考える.

スズ状態の時
スズ : スズ = スズ
スズ : カケ = カケ

カケ状態の時
カケ : スズ = カケ
カケ : カケ = スズ

スズを0
カケを1とすると

0 : 0 = 0

0 : 1 = 1

1 : 0 = 1

1 : 1 = 0

となります.
これはXOR演算です.
C言語で書くなら

printf(“0 ^ 0 = %d\n”, 0 ^ 0);

printf(“0 ^ 1 = %d\n”, 0 ^ 1);

printf(“1 ^ 0 = %d\n”, 1 ^ 0);

printf(“1 ^ 1 = %d\n”, 1 ^ 1);

SCで実行してみるとよい.
SCのXOR演算はbitXor.

//1行ずつ実行

bitXor(0,0);

bitXor(0,1);

bitXor(1,0);

bitXor(1,1);

>>0

>>1

>>1

>>0

となります.

またりさまに戻ると,自分の状態は前の人と自分の状態(0か1か)で決まります.

初期値が

0 1 1 0 1 1 0 1

として,右から見て最初の桁(1)を最初の人とすると
最初の人はカスタネットを鳴らします.
次の人はスズなので,
最初の人(1) ^ 次の人(0) => 次の人の状態が1になる

ということになります.

なので

0 1 1 0 1 1 0 1

から

0 1 1 0 1 1 1 1

になる.

またりさまではメンバーは円形に繋がっているわけなので,0番の人は7番の人と繋がっていることになります.
なので,7番目の人は0番目の人の肩を叩く(XOR演算する).

ということで,

0 1 1 0 1 1 0 1で始めると

01101101

01101111

01101011

01101011

01111011

01011011

01011011

11011011

11011010

11011010

11011110

11010110

11010110

11110110

10110110

10110110

10110111

10110101

10110101

10111101

という風に変化していく.

これは,2進数ですね.

10進数に直すと

01101101 = 109

01101111 = 111

01101011 = 107

01101011 = 107

01111011 = 123

01011011 = 91

01011011 = 91

11011011 = 219

11011010 = 218

11011010 = 218

11011110 = 222

11010110 = 214

11010110 = 214

11110110 = 246

10110110 = 182

10110110 = 182

10110111 = 183

10110101 = 181

10110101 = 181

10111101 = 189

こうなる.

SCで実装してみる
まずはクラスの設計を考える.

まずは全体の状態が必要.

player[8]
みたいに配列で扱うと簡単ですが,そんなことせずとも,プレーヤーは整数一個で表現できる.
上の表を見ると,109一個で01101101が表現できているわけです.

var decimal = 2r01101101;

decimal.postln;

>> 109

2進数表現を得るには,Integer ClassにasBinaryStringというmethodがある.

asBinaryString { | width=8 |

    ^this.asStringToBase(2, width)

}

var decimal = 109;

decimal.asBinaryString.postln;

>>01101101

8bitの場合,255が最大なので
2の8乗は256.ということは0~255.

var decimal = 255;

decimal.asBinaryString.postln;

>>11111111

var decimal = 256;

decimal.asBinaryString.postln;

>>00000000

あら,桁があふれた.

asBinaryStringでwidthが8(8bit)になっているので
widthに9を渡してやる.

var decimal = 256;

decimal.asBinaryString(9).postln;

>>100000000

でた.

asBinaryString { | width=8 |

    ^this.asStringToBase(2, width)

}

の | |という書式が気になります.なんだこれは.

Function Creation via Partial Application

f = {|x| x + 2 };

という書き方で,引数が定義できるらしい.

f = {|x| x + 2 };

f.value(3);

>> 5

f = {arg x; x + 2 };

f.value(3);

>> 5

おんなじこと.

では,またりさまを01101101(109)で始めた場合で,次の状態を計算する方法を考える.

1番目のBit(1)と2番目のBit(0)でXORして,結果を2番目のBitに保存したい.

特定のBitが0か1か取得するには

2r01101101 >> 0 & 1;

2r01101101 >> 1 & 1;

2r01101101 >> 2 & 1;

2r01101101 >> 3 & 1;

2r01101101 >> 4 & 1;

2r01101101 >> 5 & 1;

2r01101101 >> 6 & 1;

2r01101101 >> 7 & 1;

で各Bitが1か0か計算できる.

01101101 >> 2 & 1;

を例にとると
01101101を >> 2すると右にビットがずれて
00011011になる.
100000001なので

00000001

00011011

をAND演算.

00000001

00011011

00000001


ということで,3桁目は1であることが分かる.

では,3桁目が1の数字を作るには

var bin = 1 << 2;

bin.asBinaryString.postln;

>>00000100

これでできる.

ここまで押さえたところで例題.

01101101

で,3桁目と4桁目をXORしたいとする.
3桁目を取得.

var decimal = 2r01101101;

var bit = decimal >> 2 & 1;

bit.postln;

>> 1

3桁目のBitを取得して,他は0なBit表現を作る

var decimal = 2r01101101;

var bit = decimal >> 2 & 1;

bit = bit << 3;

bit.asBinaryString.postln;

>>00001000

元の数字とXORする


var decimal = 2r01101101;

var bit = decimal >> 2 & 1;

bit = bit << 3;

decimal = bitXor(decimal, bit);

decimal.asBinaryString.postln;

>> 01100101

01101101

01100101

になりました.

(>>は +>>を使うべきかもしれん)

Matari Classを実装

じゃ,これを踏まえてMatari Classを実装してみます.

必要なインスタンス変数は

  • 何人のまたりさまなのか
  • 今の状態(0010101とか)
  • 今誰の番なのか

必要なMethodは

  • 初期値と何人かを引数にしてNew
  • 一人一人のアクションが必要.
  • 状態を2進数で見たい

で実装するとこうなる.

Matari : Object

{

    var <>numofPlayer;

    var <>decimal;

    var <>index;

    

    *new{arg numofPlayer=2, decimal=0, index=0;

        ^super.newCopyArgs(numofPlayer,decimal,index);

    }

    

    matari{

        var nextIndex = (index + 1) % numofPlayer;

        index = index % numofPlayer;

        decimal = bitXor(decimal, (decimal >> index) & 1 << nextIndex);

    

        index = index + 1;

    }

    

    asBinaryString{

        ^decimal.asBinaryString(numofPlayer).value;

    }

}


かなりシンプルに書けた.

indexというのは,今何番目の人の番か?というのに使います.
そのindexによって,上記何番目の人と何番目の人をXORするかを判断.
このClassをMatari.scとでもして前回書いたClass用フォルダに入れてコマンド + Kでコンパイルして

var matari = Matari.new(8,109);

matari.asBinaryString.postln;

matari.matari();

matari.asBinaryString.postln;

matari.matari();

matari.asBinaryString.postln;

>>01101101

  01101111

  01101011


おお,ちゃんと動く.

延々書くのも頭が悪いのでforで書くと

var matari = Matari.new(8,109);

matari.asBinaryString.postln;

for(0,44100,{

    matari.matari();

    matari.asBinaryString.postln;

    }

);


きっとPlayerが一人だとBUGります.あと,32人超えると動かないはず.

んじゃ,今度はこれを使って何ができるかを考えていく.

このまたりさまのアルゴリズムを使ったシンセサイザーであるまたりさまBinaryはこちら

このエントリーをはてなブックマークに追加

022. Objective-CプログラマーのためのSC言語入門(1) ざっくり言語仕様

Posted in SuperCollider Code Reading on 3月 18th, 2008 by Norihisa Nagano – Be the first to comment

Codeばっかり読むのも飽きるので,SC言語自体も解読していく.

まずはSuperColliderってなんですか.

SuperColliderは(主に)オーディオ処理用の言語です.
テキストでプログラミングするタイプのインタープリター型言語です.

よくあるMax/MSPとの比較ですが,比較するのはちと違うんじゃないかな.
オーディオ処理という部分しか共通点が無い.
どっちかと言うと,Rubyとかと比較した方がいいと思う.
ざーっくり誤解を恐れず言えば,Rubyに音処理ができるモジュールがくっついたものと考える方がいいと思う.

で,SC言語は結構難しい.
Max/MSPとの違いは,ずばりここ.
他のテキストで書くプログラミング言語が少なくとも一個は分からないと理解できないでしょう.
いきなりSCのTutorialやっても分かるわけがない
(と少なくともSC2のとき挫折した僕は思う.CとObjective-Cをそこそこ習得した現在は理解度がまったく違う)

オブジェクト指向言語が一個と,Cの方言を一個を知っているとよい.
P言語(Perl, Python, PHP,Ruby)が一個分かると尚よし.

SuperCollider is mostly like Smalltalk but has a different syntax.
だから,Smalltalkをやったほうがいいんだろうけど
それじゃつぶしが効かないので,Smalltalkと思想が近いらしいObjective-Cをお勧めする!!

Cocoa Programmerにとっては,StandAloneのAppが作れたり,CocoaColliderがあったりするので,SCを覚えておくといろいろ便利かもしれないです.僕はインタープリター型のオーディオ処理言語を覚えておく必要性をなんとなく感じています.

SCの情報を追うと,言語に関するTutorialが少ない気がする.
なので,まずはAudioはまったく触らず,言語仕様のみ勉強していく.
(僕はSC初心者なので,間違い指摘歓迎)

Documentはこの辺を参照. 

このサーバーはいつも重いので,ローカルの
supercollider/trunk/build/Help
を見たほうが速い.

ざっくりとりあえず必要そうな言語仕様


四則演算

たし算

1 + 1(enterで実行)

>>2

Numbers
Cとほとんど変わらない.

しかし

2r01101011

でbinary!!!

2r01101011;

>>107

2r01111111111111111111111111111111

>>2147483647

1bit増やして

2r011111111111111111111111111111111

>>-1

ということで,32bitまでしか使えない.無念.
明示的に64bit~にできるようにしたい.

16rF0

で16進数

16rF0

>>240

関数の定義

x = {

   arg a, b; a + b 

};

z = x.value(1,2);

>>3

.valueが無いとerrorなので注意.
.valueが無いとダメってことは,xはObjectということですな.
関数もObject.

default引数も書ける

x = {

   arg a=1, b=2; a + b 

};

z = x.value;

>>3


printは,.printlnというmethodを使う.
明示的に.printlnしなくても,コンソールには出る(ものもある)

論理演算

if(1){

   “hoge”.println;

}


とは書けない.

if (true,“true”.postln)

と書かねばなりません.

elseを使うなら

if (false, “true”.postln, “false”.postln)

と書く.

こりゃ便利悪いぞ.言語仕様要検討で.

Characters

$A

でchar.

Symbols

‘hoge’ + ‘hoge’;

で連結できナーイ.

Strings

“hoge”

で文字列.

“hoge” + “hoge”

で連結できナーイ.(スペースが入る)

Characters,Symbols,Stringsの違いがよく分からん.

配列

[]で配列.

a = [1,2,3];

添字アクセス

a[0];

a[1];

a[2];

>>1

>>2

>>3

ビット演算

Bitwise arithmetic

number & number                bitwise and

number | number                bitwise or

number << number               bitwise left shift

number >> number               bitwise right shift

number +>> number              unsigned bitwise right shift

XORがナーイ.
XORはbitXorでやる模様.

bitXor(1,1);
>>0

Operators

いっぱいある.

sin(0.5);

>>0.4794255386042


Writing SuperCollider Classes

定義をみたいものを選択して,⌘ + Yでクラスの定義が見れるので
Pointの定義を見てみる.

Objective-Cで言うところのalloc + initは

*new

で書く.

Point.new();

>>Point( 0, 0 )

()は無くてもいい.

Point.new;

>>Point( 0, 0 )

(0,0)になるのは,

*new { arg x=0, y=0;

でdefault値が設定されているから.

なので

Point.new(10,20);

>>Point( 10, 20 )

別に,明示的にnewと書かずともOK.

Point(10,20);

>>Point( 10, 20 )

<でgetter >でsetter,<>でsetter, getterアクセス可能.

var <>x = 0, <>y = 0;

なので,

p = Point.new;

p.x;

>>0

p = Point.new;

p.x = 10;

p

>>Point( 10, 0 )

が可能.

methodで値を返すには

^ return_value;

例えばhashの定義

hash { ^ (x.hash << 1) bitXor: y.hash }


Point(10,20).hash;

>>-1769457589

x y: z

という構文が出てきた.
なんだこれは.

rrand(1,3)

1 rrand: 3

とも書ける.

こっちの例の方が分かりやすい.

max(1,2);//大きい方を返す

1 max: 2;

>>2

この構文がなんのためにあるのか分からない.
便利なのかも分からない.

演算子のオーバーロードもできる(というより,演算子もmethodというべきか?)

+ { arg delta; 

        var deltaPoint;

        deltaPoint = delta.asPoint;

        ^(this.x + deltaPoint.x) @ (this.y + deltaPoint.y)

    }

Point(10,20) + Point(20,30);

>>Point( 30, 50 )


Pointには特別扱い演算子があって

1 @ 2

>>Point( 1, 2 )

Pointになる.

NumberClassに@演算子があるから.

@ { arg aNumber; ^Point.new(this, aNumber) }


Pointで @ 演算をやると

Point(10,20) @ Point(20,30);

>>Rect(10, 20, 10, 10)

Rectになる.

    @ { arg aPoint;

        ^Rect.fromPoints(this, aPoint)

    }

で,オーバーロードされているから.

+newをもう一度見ると

    *new { arg x=0, y=0;

        ^super.newCopyArgs(x, y);

    }

superが出てくる.
親クラスのインスタンスですね.
newCopyArgsの定義を見るとObject Classに遭遇する.
Object ClassはすべてのClassが継承する.
Objective-CのNSObjectと同じこと.

    *newCopyArgs { arg … args; 

        _BasicNewCopyArgsToInstVars 

        ^this.primitiveFailed

        // creates a new instance that can hold up to maxSize 

        // indexable slots. the indexed size will be zero.

        // to actually put things in the object you need to

        // add them.

    }

_BasicNewCopyArgsToInstVars

という意味分からんものが書いてある.

こいつはPrimitive Method.
Cで実装されている関数が呼ばれるということだ.

Objective-Cで言うところのinitWith***みたいなmethod(initializer)には*を付けないといけないっぽい.というか、*が付くとクラスメソッドになる。

Codingの流儀としては
newCopyArgs
から分かるように,Objective-Cと同じように
hogeMogeMogege
という風に小文字 + 次の単語は大文字始まり
で書くのが推奨.

よし,これぐらいでクラスが書けそうだ.
クラスは ClassName.scというファイル名にするのが推奨.

Using Extensions

を見ると,

/Applications/SuperCollider/SCClassLibrary
じゃなくて,

~/Library/Application Support/SuperCollider/Extensions/

にしとくとAppを更新しても大丈夫なのでお勧めらしい.
Here is an example folder layout:

MyExtension/
classes/
myClass.sc myUGens.sc
plugins/
myUGenPlugins.scx
help/
myClass.html myUGen1.html myUGen2.html

がお勧めらしい.

TestObject : Object
{
}

こういうファイルをClassとして

sc22.png

~/Library/Application\ Support/SuperCollider/Extensions/classes/TestObject.sc
にセーブ.

⌘ + Kで,Compile Libraryを実行.

TestObject.new;

>>a TestObject

いえい,書けた.
んじゃ次はまたりさまクラスをつくることにする.

このエントリーをはてなブックマークに追加

021. SC_Node(2) DebugとHash

Posted in SuperCollider Code Reading on 3月 17th, 2008 by Norihisa Nagano – Be the first to comment

一行更新用にTwitterを導入してみました.

突っ込みたいけど,commentは敷居が高い!という人はTwitterででも.

SCCR 21回目.

19回でせっかく音を鳴らす環境を作ったので,SynthDef/s_newしたらNodeがどうなるのか追っていきます.

int Node_New(World *inWorld, NodeDef *def, int32 inID, Node** outNode)

に,ブレークポイントを設定して

sc21_1.png


+ Y (Debug)scsynthを起動します.

起動すると,まずinID 0Node_Newが呼ばれます.

SC_World.cpp: 340

int err = Group_New(world, 0, &world->mTopGroup);

ここで呼んでいる.

これは,Worldの予約番号.ということでユーザーはID 0番は使えません.

前回のMax Patchから /s_newしてみると

inIDを見てみるとちゃんと1000になっています.

ためしに,1234に変えると

sc21_2.png

ちゃんと1234になっている.

なるほど.Node /s_newに関係していることが分かりました.(当然ですが)

ここで,Node_Newに注目すると

    if (inID < 0) {

        if (inID == -1) { // -1 means generate an id for the event

            HiddenWorld* hw = inWorld->hw;

            inID = hw->mHiddenID = (hw->mHiddenID8) | 0×80000000;

        } else {

            return kSCErr_ReservedNodeID;

        }

    }

-1は特別扱いになっています.

これは,s_newのマニュアルの

http://supercollider.sourceforge.net/docs/ServerArchitecture/Server-Command-Reference.html


Synth Commands

If you send /s_new with a synth ID of -1, then the server will generate an ID for you. 

-1synthIDとして/s_newコマンドを送ると,serverIDを生成します.

の実装.おお,やっとSCのLanguage部分とソースコードが繋がってきました.

詳しく見ていこう.

0×80000000

は0xが付いているので16進数.

またまたBit演算大好きコーナー.

| は前もやったOR演算.

0×80000000

1000 0000 0000 0000 0000 0000 0000 0000

ということは,32bit目が必ず1になることになる.(1とxのOR演算は必ず1)

で,int32はsigned intなので頭のbit(32bit目)が1なら負の数になる.

つまり,自動生成されたIDは全部負の数になるはずなのだ.

試しにdebuggerで確認.

前回の/s_new sine 1234を
/s_new sine -1に変更する

sc_21_5.png

inIDが-1
sc_21_3.png

hw->mHiddenIDのDefaultは


SC_World.cpp: 273

World* World_New(WorldOptions *inOptions)

SC_World.cpp: 301

hw->mHiddenID = -8;

なので-16になる.

sc_21_4.png

ちなみにint32の範囲は

-2147483648(1000 0000 0000 0000 0000 0000 0000 0000)

 2147483647(0111 1111 1111 1111 1111 1111 1111 1111)

C言語のBit表現に関してはここが詳しい.

http://www.isc.meiji.ac.jp/~deptec/cmanapdx1.html

HiddenWorldは後回しにしてWorld_Allocを見ておきます.

SC_Node.cpp: 50

Node* node = (Node*)World_Alloc(inWorld, def->mAllocSize);

void* World_Alloc(World *inWorld, size_t inByteSize)

{

    return inWorld->hw->mAllocPool->Alloc(inByteSize);

}

void* AllocPool::Alloc(size_t inReqSize)

と,見ていくと,これはガベージコレクションっぽいなぁ.

Rubyのソースコード解説でも読んでGCの基礎を勉強しないとSCのソースだけでは理解できなさそう.

ということで,後回し.

続きを見ていく.

Node_Newの続きで気になるのは

node->mHash = Hash(inID);

Hash生成

http://ja.wikipedia.org/wiki/ハッシュ関数

ハッシュ関数 (ハッシュかんすう、hash function) とは、あるデータが与えられた場合にそのデータを代表する数値を得る操作、又は、その様な数値を得るための関数のこと。ハッシュ関数から得られた数値のことをハッシュ値または単にハッシュという。
ハッシュの用途は、「高速な検索」及び「データの検証」である。

で,このwikipediaの記事がよく出来ているので僕が解説するまでもない.

SCに話を戻すと

Hash.h: 88

// hash function for integers

inline int32 Hash(int32 inKey)

{

    // Thomas Wang’s integer hash.

    // http://www.concentric.net/~Ttwang/tech/inthash.htm

    // a faster hash for integers. also very good.

    uint32 hash = (uint32)inKey;

    hash += ~(hash << 15);

    hash ^=   hash >> 10;

    hash +=   hash << 3;

    hash ^=   hash >> 6;

    hash += ~(hash << 11);

    hash ^=   hash >> 16;

    return (int32)hash;

}

inIDint32なので,これが呼ばれます.

おっと,ここでint32について.

SCでは,独自の型が定義されています.

OS XではUInt32とかSInt32とか,ANSI Cには無い型が定義されていますが,それと似たようなもんです.

SCTypes.hに定義が書いてあって

例えばint32

SC_Types.h: 36

typedef int int32;

typedef unsigned int uint32;

いちいちunsignedと書くと暑苦しいし見づらいので便利です.

本来の目的はOS間の型によるsizeの違いの吸収でしょう.

さて,他のHash関数はどれもこのアルゴリズム使った!と書いてあって参考になると思われます.

で,このHashはどんな数字を返すのかTestを書いてみる.

void HashTest::testInteger32(){    

    int i;

    for(i = 0; i < 1000; i++){

        cout << Hash(i) << endl;

    }

}

こんなアホなコード.

結果は

1177991625

1656419744

-11726480

737174223

-935076809

-184203672

-1852194018

-1107627956

1515010735

-10442609

ぐちゃぐちゃな値が返ってきているっぽい.

Testっぽくするならとりあえず

CPTAssert(Hash(12345678) != Hash(12345679));

とか書いておけばよかろう.

Hashの基礎知識が足りないので

http://www.concentric.net/~Ttwang/tech/inthash.htm

の論文を解読していく.

このエントリーをはてなブックマークに追加

020. SC_Node(1) ポインタのポインタと構造体

Posted in SuperCollider Code Reading on 3月 16th, 2008 by Norihisa Nagano – Be the first to comment

なかなか面白い展開にならないSCCR,早くも20回目.

辛抱強く5年以内の完結を目指し,じっくり読んでいくぞ.


InterfaceTable_Init();に戻る.

SC_World.cpp: 196

ft->fNodeEnd = &Node_End;

SC_Node.cpp: 243

void Node_End(Node* inNode)

{

    inNode->mCalcFunc = (NodeCalcFunc)&Node_Delete;

}

SC_InterfaceTable.h: 126

#define NodeEnd (*ft->fNodeEnd)

InterfaceTableだから,こういうPluginから使うinterfaceをもりもり作っているのがvoid InterfaceTable_Init()です.

ということでNodeを見ていきます.

Nodeは構造体.

名前のとおりNode

SC_Node.h: 27

typedef void (*NodeCalcFunc)(struct Node *inNode);

struct Node 

{    

    int32 mID;

    int32 mHash;

    struct World *mWorld;

    struct NodeDef *mDef;

    NodeCalcFunc mCalcFunc;

    struct Node *mPrev, *mNext;

    struct Group *mParent;

    

    int32 mIsGroup;

};

typedef struct Node Node;

NodeがあればGraphもある.Groupもあるので先に押さえておく.

SC_Graph.h: 28

struct Graph 

{

    Node mNode;

        

    uint32 mNumWires;

    struct Wire *mWire;

    

    uint32 mNumControls;

    float *mControls;

    float **mMapControls;

        

    uint32 mNumUnits;

    struct Unit **mUnits;

    

    int mNumCalcUnits;

    struct Unit **mCalcUnits; // excludes i-rate units.

    int mSampleOffset;

    struct RGen* mRGen;

    

    struct Unit *mLocalAudioBusUnit;

    struct Unit *mLocalControlBusUnit;

    

    float mSubsampleOffset;

};

typedef struct Graph Graph;

SC_Group.h: 27

struct Group {

    Node mNode;

    

    Node *mHead, *mTail;

};

typedef struct Group Group;

まずはNodeがどうやって作られるのかを追う.

SC_Node.cpp: 35

int Node_New(World *inWorld, NodeDef *def, int32 inID, Node** outNode)

である.

SC_Graph.cpp: 77

int Graph_New(struct World *inWorld, struct GraphDef *inGraphDef, int32 inID, 

            struct sc_msg_iter* args, Graph** outGraph)

{

    Graph* graph;

    int err = Node_New(inWorld, &inGraphDef->mNodeDef, inID, (Node**)&graph);

    if (err) return err;

    Graph_Ctor(inWorld, inGraphDef, graph, args);

    *outGraph = graph;

    return err;

}

SC_Group.cpp: 50

int Group_New(World *inWorld, int32 inID, Group** outGroup)

{    

    Group *group;

    int err = Node_New(inWorld, &gGroupNodeDef, inID, (Node**)&group);

    if (err) return err;

    

    group->mNode.mCalcFunc = (NodeCalcFunc)&Group_Calc;

    group->mNode.mIsGroup = true;

    group->mHead = 0;

    group->mTail = 0;

    inWorld->mNumGroups++;

    *outGroup = group;

    

    return kSCErr_None;

}

Node_NewにはGroupGraphが渡されている.なんだこれは.

結論から書くと,GroupGraphNode mNodeを持っていて,Node_Newすると作られたNodemNodeに代入される.

なんで!!

ということで,これらを単純化したコードを書いて検証.

#include <iostream>

#include “SC_Types.h”

struct Node 

{    

    int32 mID;    

};

typedef struct Node Node;

struct Group {

    Node mNode;    

    Node *mHead, *mTail;

};

typedef struct Group Group;

void Node_New(int32 inID, Node** outNode){

    Node* node = (Node*)malloc(sizeof(Node));    

    node->mID = inID;

    

    *outNode = node;

}

int main (int argc, const char * argv[]) {    

    Group *group;

    Node_New(10,(Node**)&group);    

    printf(“group->mNode.mID = %d\n”,group->mNode.mID);    

    return 0;

}

なぜかgroup->mNode.mID10が入っている.

こういう風にアドレスを表示するように書き換えてみるといい.

void Node_New(int32 inID, Node** outNode){

    printf(”   **outNode = %p\n”,**outNode);    

    Node* node = (Node*)malloc(sizeof(Node));

    node->mID = inID;

    

    *outNode = node;

}

int main (int argc, const char * argv[]) {    

    Group *group;    

    printf(“group->mNode = %p\n”,group->mNode);    

    Node_New(10,(Node**)&group);

    

    printf(“group->mNode.mID = %d\n”,group->mNode.mID);    

    return 0;

}

group->mNode = 0xc483ec89

   **outNode = 0xc483ec89

同じ.つまりは,(Node**)&groupgroupの先頭のアドレスを渡すので,group->mNodeを渡しているのと同じということですね.

&groupはポインタのアドレスになるので,(Node**)になるわけです.

printf(“group->mNode = %p\n”,group->mNode);    

printf(“      *group = %p\n”,*group);

と書いてみると

group->mNode = 0xc483ec89

      *group = 0xc483ec89

ということで,結局*outNodeで実体をたぐりよせて代入すればmNodenodeになる.

うーむ,うまく説明できないということはポインタが分かってないっぽい.

深遠なりC.


このエントリーをはてなブックマークに追加

019. scsynthのみで音を鳴らす

Posted in SuperCollider Code Reading on 3月 15th, 2008 by Norihisa Nagano – Be the first to comment

今日も元気にSCCR.19回目.

さて,続きを見ると

SC_World.cpp: 196

ft->fNodeEnd = &Node_End;

 

ということで,これからはNodeとかGraphとかを見ていくのですが,ソースだけじゃよく分からん.

なのでSuperColliderの実際の動作からソースを追うことにする.

今回はその環境を構築していきます.

SCはversion 3から,SynthDefを作って,それを操作するようになりました.

操作にはserver commandを使います.

SuperCollider Server Synth Engine Command Reference

http://supercollider.sourceforge.net/docs/ServerArchitecture/Server-Command-Reference.html

 

まずはSynthDefを作ります.

SynthDefはLanguageが無いと作れないはず.

ということで,SuperCollider.appを起動して

(
SynthDef(“sine”, {
     var osc;
     osc = SinOsc.ar(440, 0, 0.3);
     Out.ar(0, osc);
}).writeDefFile;

)


 

を選択してenter.(returnではない)

そうすると

~/Library/Application\ Support/SuperCollider/synthdefs/sine.scsyndef 

にファイルが作られます.

 

こいつがSynthの定義(Synth Definition)です.

 

このSynthDefをNewすると音が鳴るわけですが,それにはOSC(Open Sound Control)のMessageを送る必要があります.

ということで,Max/MSPのudpsend Objectを使う.

 

こういうパッチを書く.


sc_19_1.png

 

udpsendなので,-u オプション(UDPを使う)でscsynthを起動する.portはなんでもいいけど,max patchと合わせる.

(ここでは8888にしてある)

 

Xcodeから起動した方がDebugできるし便利なのでXcodeの実行可能ファイルのオプションに引数を設定する.

 

sc_19_2.png

 

で,ビルド実行

max patchの/d_loadをクリックして,/s_newをクリックすると音が鳴る.

/n_freeで開放./quitでscsynth終了.

/d_loadはscsynthの起動オプション

 

D <load synthdefs? 1 or 0>         (default 1)

 

で,defaultで1なので,sine.synthdefは勝手に読み込まれるので実は必要無し.


 この辺は8ちゃんの 

http://www.iamas.ac.jp/~tn800/sc3/trans/tutorial.html

turorial訳を見ておくとよい.

よーし,これで最もシンプルなsine波を鳴らす動作を追っていく準備ができました.


音やる人はMax/MSPを覚えておくと何かと便利です.

このエントリーをはてなブックマークに追加

018. SC_CoreAudio(server_timeseed) XOR演算と2の補数

Posted in SuperCollider Code Reading on 3月 14th, 2008 by Norihisa Nagano – Be the first to comment

void InterfaceTable_Init();の続き.

SC_World.cpp: 194
ft->fRanSeed = &server_timeseed;


int32 server_timeseed()

{

    static int32 count = 0;

    int64 time = AudioGetCurrentHostTime();

    return (int32)(time >> 32) ^ (int32)time ^ count–;

}

でました,XOR演算.

Bit演算大好きのコーナー
XOR演算は

0 ^ 0 0
0 ^ 1 1
1 ^ 0 1
1 ^ 1 0

こうなる.同じなら0,違えば1.

time >> 32は32bit(4294967296)で割り算.

timeが224976432668810だとして
>> 32すると

52381(1100110010011101)

(uint32)timeは

1750737034(1101000010110100010000010001010)

XORすると

0000 0000 0000 0000 1100 1100 1001 1101 (52381)
0110 1000 0101 1010 0010 0000 1000 1010 (1750737034)
                                     ↓
0110 1000 0101 1010 1110 1100 0001 0111 (1750789143)

さて,なんでcount–になっているのか.
staticなので,呼ばれるたびにcountは1づつ減っていく.
後ろに–なので最初は0ですが,次に-1になった場合を考える.

http://ja.wikipedia.org/wiki/2の補数

を参考に,-1の2進表現を求める
intは32bitなので

1 0000 0000 0000 0000 0000 0000 0000 0000

- 0000 0000 0000 0000 0000 0000 0000 0001

  1111 1111 1111 1111 1111 1111 1111 1111

ということで,4294967295になる.

1750789143と-1をXOR

0110 1000 0101 1010 1110 1100 0001 0111

1111 1111 1111 1111 1111 1111 1111 1111

1001 0111 1010 0101 0001 0011 1110 1000

-1750789144(2544178152)
になる.
見事にbitが反転していることに注目.

で,結局何を意味しているのか分かりませんが,seedという名前のとおり
Randomで使うSeedをごちゃごちゃやっているのでしょう.
(知らないけど,こういうrandomのアルゴリズムがあるのだろう)

このエントリーをはてなブックマークに追加

017. SC_World(scprintf) SCのコンソールアウト・ハンドリング

Posted in SuperCollider Code Reading on 3月 13th, 2008 by Norihisa Nagano – Be the first to comment

DSPコードに辿り着くのはいつのことやら,せっせと日々SCCR.

void InterfaceTable_Init()の続き.

SC_World.cpp: 192

ft->fPrint = &scprintf;

fPrintは関数のポインタ型

int (*fPrint)(const char *fmt, ...);

scprintfとはなんぞや.scprintfの実装はこれ

SC_World.cpp: 1231

int scprintf(const char *fmt, …)

{

va_list vargs;

va_start(vargs, fmt); 

if (gPrint) return (*gPrint)(fmt, vargs);

    else return vprintf(fmt, vargs);

}

これは可変長引数ですね.C言語だと,引数はたとえば,func(int a, int b);みたいに固定で使いますが

printfなんかは

printf(“%d”,intA);

printf(“%d,%d”,intA,intB);

printf(“%d,%d,%f”,intA,intB,floatA);

printf(“%d,%d,%f,%f”,intA,intB,floatA,floatB);

 みたいに引数が何個でも書ける.これは,printfの定義が

printf(const char* fmt, int);

printf(const char* fmt, int a, int b);

と無限にされているわけではなくて

 

int printf(const char * __restrict, …)

という風に,…の部分は可変にできるわけです.可変長引数を使う関数の定義は,function(第一引数, …)という風に書く.

C言語ポインタ完全制覇にdefaultで改行を行うprintfであるtiny_printfの例が載っています.

引用.

void tiny_printf(char *format,…){

    int i;

    va_list ap;

    

    va_start(ap, format);

    for(i = 0; format[i] != ‘′; i++){

        switch(format[i]){

            case ’s’:

                printf(“%s”,va_arg(ap, char*));

                break;

            case ‘d’:

                printf(“%d”,va_arg(ap, int));

                break;

            default:

                assert(0);

        }

    }

    va_end(ap);

    putchar(‘n’);

}

hoge123456

12hoge123456

“sdd”でstring,int,intという風に型を指定しているわけですね.
va_startを呼んで,var_list apを初期化.(お決まり)
va_argでapから型を指定して順番に可変長引数を1個ずつ取得.
va_endで終了.(お決まり)
ここでのポイントは
・va_argで型を指定するのだが,型を自動で判定することはできない.
・なので,型をちゃんと把握できるようにしておかないとバグる.

printfをwrapしたい時にはこういう手法もある.C言語ポインタ完全制覇をちとアレンジ.

int wrap_printf(char* fmt, …){

    va_list ap;

    va_start(ap, fmt);

    int result = vfprintf(stderr,fmt,ap);

    va_end(ap);

    return result;

}

 


vfprintfはva_listを受け取ることができるprintf.
scprintfもやってることは同じ.gPrintはglobal変数で

SC_World.cpp: 56

PrintFunc gPrint = 0;

SC_World.cpp: 1225

void SetPrintFunc(PrintFunc func)

{

    gPrint = func;

}

 で設定できるようになっているので,gPrintがsetされていればそっちに投げるだけ.

ft->fPrintは

SC_InterfaceTable.h: 124

#define Print (*ft->fPrint)

 とマクロで定義されている.Printの使用例はSynth.xcodeprojには無し.

他で使うのでしょう.(Pluginでごろごろ使われております)

このエントリーをはてなブックマークに追加

016. SC_Samp(2) wavetable as signal

Posted in SuperCollider Code Reading on 3月 11th, 2008 by Norihisa Nagano – Be the first to comment

SignalAsWavetableの処理.

inはsignal.
で,inのindexと次のindex + 1がvar1,var2.
偶数index var1の二倍 – var2
奇数index var2 – var1

なので変化量 + var1と,変化量という大小大小のギザギザになる.

つまり,なめらかに変化する波形であればその波形の中心からsampleの値までの間を塗りつぶしたような表示になる.
試しに三角波でやるとこうなる.

sc_16.png

最後の2個は
val1 = in[inSize-1];
val2 = in[0];

なので,最後と最初
gSineWavetable[16382] = -0.001534
gSineWavetable[16383] = 0.000767

これは何に使うのだろう.
例えばPluginの
PanUGens.cpp: 225

unit->m_leftamp  = unit->m_level * ft->mSine[2048 - ipos];

というのがある.

SC全体で使う共通のデータらしい.

gInvSineは

gInvSine[0] = gInvSine[kSineSize/2] = gInvSine[kSineSize] = kBadValue;

int sz = kSineSize;

int sz2 = sz>>1;

for (int  i=1; i<=32; ++i) {

gInvSine[i] = gInvSine[sz-i] = kBadValue;

gInvSine[sz2-i] = gInvSine[sz2+i] = kBadValue;

}

ということで,一定のindexがkBadValueになっている.

SC_Constants.h: 44

const float kBadValue = 1e20f; // used in the secant table for values very close to 1/0

eというのは10の何乗という意味で

1000000は 1e6
0.000001は10のマイナス6乗なので,1e-6

2e4と書くと,10^4 * 2 = 20000
になる.

で,1e20fがbadValue?よくわからん.
ともあれ,これを共通のbadValueとして使うのでしょう.

SignalAsWavetableはWavetableAsSignalで元のSignalに戻せる.

こういうTestを書いてみるといい.

void SampTest::testSignalAsWavetable(){    

    float signals[1000];

    float wavetable[2000];

    

    srand(time(0));

    

    for(int i=0; i < 1000; i++){

        signals[i] = (rand() % 20000 / 10000.0) – 1.0;

        cout << signals[i] << endl;

    }

    

    float resignals[1000];

    SignalAsWavetable(signals, wavetable, 1000);

    WavetableAsSignal(wavetable, resignals, 1000);

    

    for(int i=0; i < 1000; i++){

        CPTAssert(fabs(signals[i] – resignals[i]) < 0.00001);

    }

}

浮動小数なので多少の誤差がでるので,差をfabsで正数にして比較.

SC_Sampはこの程度でいいでしょう.

このエントリーをはてなブックマークに追加

015. SC_Samp(1) signal as wavetable

Posted in SuperCollider Code Reading on 3月 10th, 2008 by Norihisa Nagano – Be the first to comment

さて,前回まででEngineFifoを押さえたので,やっとRunに戻れるのですが
これから先のRunを追うのは非常にややこしいことになっています.

Runの中でOSCのMessageの実行管理がやられているのですが,その辺がかなりややこしい.

ということで,順番に追う必要も無いし,Runが呼ばれるまでは押さえたので
一端SC_CoreAudioを離れてmainから順番に先に見ていきます.

もう一度,main関数から見ていきます.
scsynth_main.cpp: 120
006. SC_CoreAudioを読み解く(2) scsynthの起動optionsでもやりましたが,mainは起動オプションをパースして,World構造体を作ります.

SC_World.cpp: 273

World* World_New(WorldOptions *inOptions)

を追っておく.

SC_World.cpp: 278

static bool gLibInitted = false;

なので,どこでWorld_Newを呼び出してもWorld_New中でgLibInittedは値を保持し続けます.
static変数はSingletonをやるときによく使われます.

初回はgLibInittedがfalseなので,各種初期化関数がもりもり呼ばれます.

SC_World.cpp: 280

InterfaceTable_Init();

initialize_library();

initializeScheduler();

SC_World.cpp: 183

void InterfaceTable_Init();

gInterfaceTableはglobal変数

SC_World.cpp: 55

InterfaceTable gInterfaceTable;

InterfaceTableは構造体
SC_InterfaceTable.h: 36

struct InterfaceTable

SC_World.cpp: 187

ft->mSine = gSine;

こいつはなんだろう.
SC_Samp.h: 30

extern float32 gSine[kSineSize+1];

Samp.cpp: 27

float32 gSine[kSineSize+1];

Samp.cpp: 77

void AudioLibInit::FillTables()

で初期化されています.

Samp.cpp: 70

AudioLibInit gAudioLibInit;

という定義があるので,Samp.hがincludeされた段階で
AudioLibInit::AudioLibInit()が呼ばれてFillTables()が呼ばれます.

FillTablesは何をしているのか.

1L (Long型の1)を 29シフトなので
頭が1で0が28個.
bin 10000000000000000000000000000
dec 536870912

twopiは
SC_Constants.h: 31

const double twopi  = pi * 2.;

gPMSine[i] = (float)(d * pmf);

なので,gSineとgPMSineはレンジが違うだけ.(-1.0 ~ 1.0と,-85445659.447054 ~ 85445659.447054)
gSine,gInvSine,gInvSineを計算した後gSineからgSineWavetableがSignalAsWavetableで作成されています.


void SignalAsWavetable(float32* signal, float32* wavetable, long inSize)

こいつは何をやっているのか.
まずはvisualに確認してみる.
Quartzでさくっと描いてしまう.

sc_15.png

おお,なるほど.

VisualizeするCodeはこちら.

Download -> Samp.Cpp.app.zip

このエントリーをはてなブックマークに追加

014. SC_CoreAudioを読み解く(10) MsgFifoのアルゴリズム

Posted in SuperCollider Code Reading on 3月 2nd, 2008 by Norihisa Nagano – Be the first to comment

よく分からんときはNumbersで時系列変化を追う

MsgFifoのWriteとPerformとFreeの役割を見てみます.

#defineがあると分かりにくいし,CompareAndSwapも意味は m*** = nextなので

template <class MsgType, int N>

class MsgFifo

{

public:

    MsgFifo()

            : mReadHead(0), mWriteHead(0), mFreeHead(0)

    {}

    void MakeEmpty() { mFreeHead = mReadHead = mWriteHead; }

    bool IsEmpty() { return mReadHead == mWriteHead; }

    bool HasData() { return mReadHead != mWriteHead; }

    bool NeedsFree() { return mFreeHead != mReadHead; }

    bool Write(MsgType& data)

    {

        unsigned int next = NextPos(mWriteHead);

        if (next == mFreeHead) return false; // fifo is full

        mItems[next] = data;

        mWriteHead = next;

        return true;

    }

    void Perform() // get next and advance

    {

        while (HasData()) {

            unsigned int next = NextPos(mReadHead);

            mItems[next].Perform();

            mReadHead = next;

        }

    }

    void Free() // reclaim messages

    {

        while (NeedsFree()) {

            unsigned int next = NextPos(mFreeHead);

            mItems[next].Free();

            mFreeHead = next;

        }

    }

private:

    int NextPos(int inPos) { return (inPos + 1) & (N – 1); }

    UInt32 mReadHead, mWriteHead, mFreeHead;

    MsgType mItems[N];

};

に書き換えちゃう.

書き換えてもTest Caseはもちろん通る.
もちろん,書き換える必要性は無い.けど見やすくするのはいいと思う.

で書き換えても
UInt32 mReadHead, mWriteHead, mFreeHead;
が何のために使われて,どういう動作になるのか,これを見ても(僕は)ぱっと分からない.
こういう時はNumbersにこういう表を書いてみると一発で分かる.

sc_14.png

つまりはPerform用のindexであるmReadHeadとFree用のindexであるmFreeHeadがあって
FifoMsg::FreeはFifoMsg::Perform実行後しか呼ばれない.(呼べない)

というアルゴリズムになっている.

気になるのは1024個を超えてWriteした時はindexがひっくり返るのでおかしなことになりそげ.

unsigned int next = NextPos(mWriteHead);

if (next == mReadHead) return false; // fifo is full

なので,例えば1024(というか1023)を超えてWriteして,Perform()を実行していないとmReadHeadは0.

このときindex(next)が0になるとここでひっかかるのでOK.

Fifoだからfirst in first out.
http://ja.wikipedia.org/wiki/FIFO

ということで,MsgFifoはPerform後にFreeを呼び出すQueue Classということでいいでしょう.

次回からはこのMsgFifoはいったい何に使うのかを追っていきます.
感のいい人はOSCのMessageだろうと気付いているでしょう.

このエントリーをはてなブックマークに追加