Skip to content

Instantly share code, notes, and snippets.

@tn800
Last active January 3, 2019 07:13
Show Gist options
  • Save tn800/aa6d5b0e91df6c833255 to your computer and use it in GitHub Desktop.
Save tn800/aa6d5b0e91df6c833255 to your computer and use it in GitHub Desktop.
SCの公式ヘルプの邦訳+注釈。

#ユニット・ジェネレータを書く

##ユニット・ジェネレータ・プラグインの動作

ユニット・ジェネレータ・プラグインは、サーバーの起動時に読み込まれます。ユニット・ジェネレータ・プラグインは、C++で記述されたライブラリで、直接ロードされます。各ライブラリには、ひとつ、または複数のUGenの定義が含まれています。

プラグインはUGenだけでなく、例えばバッファを満たす("/b_gen”)のようなコマンドも定義することができまこれらは、シンセ・サーバーの起動時に読み込まれるため、プラグインをコンパイルした後は、サーバーを再起動する必要があります。 。 ##The Entry Point:エントリポイント (訳注:エントリポイントは、プログラムを実行するうえで、一番最初に実行される場所のこと。例えばC言語だとmain関数とか。以下の説明は読んでいるだけだと分かりにくいので、下の実際のC++コードを見ながら読むといいです。)ライブラリが読み込まれると、サーバーはPluginLoad()macroに定義されているライブラリの中の関数を呼び出します。このエントリポイントには二つの役割があります:

  • グローバル変数のInterfaceTableへのポインタに格納する
  • ユニット・ジェネレータを登録する

InterfaceTableの中の関数を呼び出すことによりユニット・ジェネレータは定義され、ユニット・ジェネレータの名前、サイズ、Cのデータ構造体、コンストラクタとデストラクタのための関数のポインタが渡されます。

(訳注)コンストラクタとデストラクタ、Objective-Cでいうところのalloc_init(インスタンスの作成)とdealloc(インスタンスを破壊)にあたる感じ 参考:http://www.atmarkit.co.jp/fdotnet/csharp_abc/csharp_abc_011/csharp_abc01.html

プロセスを簡易にする為に使われる4つのマクロがあります。 DefineSimpleUnit “シンプル”なユニットジェネレータを定義 DefineDtorUnit デストラクタを伴うユニット・ジェネレータの定義 DefineSimpleCantAliasUnit “シンプル”なユニットジェネレータを定義、インプットとアウトプットのバッハはエイリアスできない DefineDtorCantAliasUnit デストラクタを伴うユニット・ジェネレータの定義、インプットとアウトプットのバッハはエイリアスできない

これらのマクロは、特殊な命名規則による: ・ユニット・ジェネレータ・ストラクトは、PluginNamのように名付ける ・ユニット・ジェネレータ・コンストラクタは、PluginName_Ctorと名付ける ・ユニット・ジェネレータ・デストラクタは、PluginName_Dtorと名付ける

##シンプルなユニット・ジェネレータ・プラグイン:A Simple Unit Generator Plugin ユニット・ジェネレータ・プラグインには、二つのパートが必要になります:一つは、C++部分で動的に読み込まれたライブラリとしてロードされるサーバー・サイドのコードを実装するものです。もう一つはSCLangのクラスで、SynthDefを作るのに必要になります。以下の例ではシンプルなノコギリ波のオシレータを定義しています。

C++-side Definition of Unit Generators:C++での定義

シンプルなユニット・ジェネレータを定義するC++のソースコードです。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
#include "SC_PlugIn.h" 
// SCのソースの "SC_PlugIn.h”ファイルを読み込む。
// このファイルはsupercolliderのソースコードの
// include > plugin_interfaceフォルダに入っています。
// 主に大文字のみで書かれているものは、
// このヘッダファイルを辿るとどこかに定義されています。

// ホスト(サーバー)への関数へのポインタを含むInterfaceTableを変数 ftに。
static InterfaceTable *ft;

// ユニット・ジェネレータの状態を保持する構造体を宣言。
// ここではMySawという名前の構造体を宣言していて
// その構造体のメンバとして、mPhaseとmFreqMulを定義しています。
struct MySaw : public Unit
{
    double mPhase; // オシレータのフェーズ。-1から1
    float mFreqMul; // 周波数に乗算する定数
};

// ユニット・ジェネレータの関数を宣言する。
// MySaw_next_a、MySaw_next_kは、それぞれオーディオ・レート用とコントロール・レート用。
// MySaw_Ctorは、上のマクロの説明ででてきたやつで、
// ユニット・ジェネレータ・コンストラクタ、つまりインスタンスを初期化して作る部分。
static void MySaw_next_a(MySaw *unit, int inNumSamples);
static void MySaw_next_k(MySaw *unit, int inNumSamples);
static void MySaw_Ctor(MySaw* unit);


//////////////////////////////////////////////////////////////////

// 上で宣言した関数を実装している部分。
// Ctorは、ユニット・ジェネレータの初期化のために呼ばれる。
// 実行されるのは1度だけ。

// Ctorは、通常3つのことをします。
// 1. 演算する関数をセットする。
// 2. ユニット・ジェネレータの状態変数を初期化する
// 3. 出力する1サンプルを演算する

void MySaw_Ctor(MySaw* unit)
{
    // 1. 演算する関数をセットする。
    // オーディオレートかコントロールレートかを条件判断して、それぞれ関数をセットする
    // INRATEは、SC_Unit.hで定義(define)されていて、入力のレートを得ることができる

    if (INRATE(0) == calc_FullRate) {
        // もし周波数アーギュメントがオーディオ・レートなら
        SETCALC(MySaw_next_a);
    } else {
        // もし周波数アーギュメントがコントロール・レート(またはスカラ)なら
        SETCALC(MySaw_next_k);
    }

    // 2. ユニット・ジェネレータの状態変数を初期化する。
    // 周波数に乗算する定数を初期化
    unit->mFreqMul = 2.0 * SAMPLEDUR;
    // SAMPLEDURは、おそらくSCのSampleDurクラスと同じで1サンプルの長さを返す。1/SampleRate。
    // オシレータのフェーズの初期値を取得
    unit->mPhase = IN0(1);

   // ここにでてきたunit->mFreqMulと下の->mPhaseの意味は、
   // 構造体のメンバー(要素)を呼び出しています。
   // unitは、void MySaw_Ctor(MySaw* unit)で引数になっているやつです。
   // その前のMySawは、上で定義してある構造体のことですね。
   // 参考:http://www7b.biglobe.ne.jp/~robe/cpphtml/html01/cpp01060.html


    // 3. 出力の1サンプルを演算
    MySaw_next_k(unit, 1);
}


//////////////////////////////////////////////////////////////////

// The calculation function executes once per control period
// which is typically 64 samples.

// calculation function for an audio rate frequency argument
void MySaw_next_a(MySaw *unit, int inNumSamples)
{
    // OUTは、SC_Unit.hに定義(define)されていて、出力バッファへのポインタを返す
    float *out = OUT(0);
    // INは、SC_Unit.hに定義(define)されていて、入力バッファへのポインタを返す
    float *freq = IN(0);

    // phaseとfreqmul定数を構造体から取得し、ローカル変数に保存する
    // The optimizer will cause them to be loaded it into a register.
    float freqmul = unit->mFreqMul;
    double phase = unit->mPhase;

    // perform a loop for the number of samples in the control period.
    // If this unit is audio rate then inNumSamples will be 64 or whatever
    // the block size is. If this unit is control rate then inNumSamples will
    // be 1.
    for (int i=0; i < inNumSamples; ++i)
    {
        // out must be written last for in place operation
        float z = phase;
        phase += freq[i] * freqmul;

        // these if statements wrap the phase a +1 or -1.
        if (phase >= 1.f) phase -= 2.f;
        else if (phase <= -1.f) phase += 2.f;

        // write the output
        out[i] = z;
    }

    // store the phase back to the struct
    unit->mPhase = phase;
}

//////////////////////////////////////////////////////////////////

// calculation function for a control rate frequency argument
void MySaw_next_k(MySaw *unit, int inNumSamples)
{
    // get the pointer to the output buffer
    float *out = OUT(0);

    // freq is control rate, so calculate it once.
    float freq = IN0(0) * unit->mFreqMul;

    // get phase from struct and store it in a local variable.
    // The optimizer will cause it to be loaded it into a register.
    double phase = unit->mPhase;

    // since the frequency is not changing then we can simplify the loops
    // by separating the cases of positive or negative frequencies.
    // This will make them run faster because there is less code inside the loop.
    if (freq >= 0.f) {
        // positive frequencies
        for (int i=0; i < inNumSamples; ++i)
        {
            out[i] = phase;
            phase += freq;
            if (phase >= 1.f) phase -= 2.f;
        }
    } else {
        // negative frequencies
        for (int i=0; i < inNumSamples; ++i)
        {
            out[i] = phase;
            phase += freq;
            if (phase <= -1.f) phase += 2.f;
        }
    }

    // store the phase back to the struct
    unit->mPhase = phase;
}


// the entry point is called by the host when the plug-in is loaded
PluginLoad(MySaw)
{
    // InterfaceTable *inTable implicitly given as argument to the load function
    ft = inTable; // store pointer to InterfaceTable

    DefineSimpleUnit(MySaw);
}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
###SCLang-side Definition of Unit Generators

SynthDefを作る為にSCLangで扱うクラスを記述する必要があります。
MySawUgenのアーギュメントは、freqとphaseです。multiNewメソッドは、マルチチャンネル拡張を扱う(handle★)います。maddメソッドは、mulとaddアーギュメントをサポートしてくれ、必要に応じてMulAddUGenを生成してくれます。作成するクラスは、mulやaddアーギュメントが必須ではありませんが、これらが用意してあれば、ユーザにとっては便利です。sclang claSuperColliderSuperColliderの作成については、ヘルプファイル「Writing Classes」を参照してください。


// without mul and add.
MySaw : UGen {
    *ar { arg freq = 440.0, iphase = 0.0;
        ^this.multiNew('audio', freq, iphase)
// mulとaddを付け加えたい場合は以下のようにするのと
// 上のarg宣言と初期値も加える。
// ^this.multiNew('audio', freq, iphase).madd(mul, add)

    }
    *kr { arg freq = 440.0, iphase = 0.0;
        ^this.multiNew('control', freq, iphase)
    }
}

###Building Unit Generator Plugins ユニット・ジェネレータ・プラグインのビルド プラグインのビルドの最も便利な方法は、クロス・プラットフォームのビルドシステムであるcmake( http://www.cmake.org)を用いたものです。 サンプルファイルをcmakeを使ってビルドするには、以下のコードをCMakeLists.txtに記述します。★

//////////////////////////////////////// cmake_minimum_required (VERSION 2.8) project (MySaw)

include_directories(${SC_PATH}/include/plugin_interface) include_directories(${SC_PATH}/include/common) include_directories(${SC_PATH}/external_libraries/libsndfile/)

set(CMAKE_SHARED_MODULE_PREFIX "") if(APPLE OR WIN32) set(CMAKE_SHARED_MODULE_SUFFIX ".scx") endif()

add_library(MySaw MODULE MySaw.cpp) ////////////////////////////////////////

##Coding Guidelines:コーディングのガイドライン ユニット・ジェネレータ・プラグインは、リアルタイムのコンテキストから呼び出されます。 つまりこれが意味するのは、オーディオのドロップアウトを避ける為に、特に注意する必要があります。 ####Memory Allocation:メモリの確保 “ malloc”や” free”、” free”や” delete”を通じてOSから確保しないこと。その代わりに、リアルタイムにメモリを確保することのできる” RTAlloc”や” RTFree”を使うこと。 ####STL Containers:STLコンテナ STLコンテナを使う事は通常はお薦めできません。なぜならこれらは内部的にメモリを確保するからです。STLコンテナを使用することのできる唯一の方法は、サーバに割り当てる関数にマップするSTLを提供することである。 ####Blocking API Calls:ブロッキングAPIの呼び出し ユニット・ジェネレータは、現在のスレッドの実行をブロックする可能性のあるコードを呼び出すべきではありません。具体的には、システムコールは避けるべきである。もし他のスレッドとの同期が必要な場合、これは、lock-free mannerで行われなければならない。 (補足)システムコール:http://ja.wikipedia.org/wiki/システムコール

##Thread Safety:スレッドの安全性

SuperColliderのサーバーは、ふたつの異なるものが実装されています。scsynthは、昔からあるサーバーで、supernovaはマルチプロセッサのオーディオ生成の為に新しく実装されたサーバーです。supernovaのプラグインは、複数のスレッドから同時に呼び出される可能性があるので、グローバルデータ構造への書き込みアクセスを同期する必要があります。 ####Shared Global Data Structures:グローバル・データ構造の共有 ユニット・ジェネレータは、書き込まれるデータ構造を共有すべきではありません。 読み取り専用(read-only)のためにグローバルなデータ構造を使用しても安全ですが(例えば、異なるユニット・ジェネレータは同じコンスタントのウェーブテーブルを使用することができるでしょう)、ユニット・ジェネレータによりモディファイされるデータ構造は、他のインスタンス間で共有されるべきではありません。 ####Resource Locking:リソースのロック SuperColliderのバッファとバスは、グローバル・データ構造であり、アクセスは同期する必要があります。これは、リーダー/ライターのスピンロックを用いて内部的に行われます。これは、 「SC_Unit.h」に定義されている”ACQUIRE_”、 “RELEASE_” 、および”LOCK_”マクロを用いて行われます。

###Deadlock Prevention:デッドロックの回避

  • デッドロックを回避するために、簡単なデッドロック防止schemeは、以下の制約に基づいて、実装されています。必要ときのみリソースをロックする:いくつかのユニット・ジェネレータは、同時に複数のリソースへのアクセスを必要とします。この決まりの主な例外は、同時に複数のバッファにアクセスするFFT Chain関連のユニット・ジェネレータです。バッファとバスの両方に同時にアクセスする必要のあるユニット·ジェネレータは、今のところ存在していません。
  • リーダーのロックが可能な場合を取得します。複数のユニット・ジェネレータは、同時に同じリソースへのリーダーロックを取得することができるので、それらの使用は競合を減らします。
  • リソースは明確に定義された順序で取得される必要があります:バス、ハイ・インディックスのリソースを伴うバッファ、ロー・インディックスのリソースを伴うバッファの順に取得する必要があります。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment