以前faustからJUCEのプラグイン向けにコードを生成してAudio Unitのプラグインを作りましたが、GUIなどはJUCE側で自由に作った方が都合が良いことも多いと思います。そこで手始めに、faustのexportで選択できる「素のソースコード」がどんなものかを見てみることにしました。

faustファイルをCコードで書き出し

以前はJUCE形式で書き出しましたが、今回はPlatformを”source”にして書き出しをしてみます。

そうすると、以下のようにいくつかの”Architecture”が選択できます。

今回は、もっとも汎用性が高いと思われるCでどのようなコードが出力されるか確認していきます。

Cコードとして出力されるもの

以前作成した4オペのFM音源だと、587行の1つのcファイルが出力されました。内容をざっとみてみると、

  • 主にUIから入力されるパラメータを保管するであろうmydspという名の構造体
  • UIコンポーネントの初期化や、上記パラメータとの接続を行うbuildUserInterfacemydsp関数
    • ただし、引数として規定されているUIGlue型が解決できない、とコンパイラに怒られています
  • 実際に波形を計算すると思われるcomputemydsp関数

などが見つかります。コンパイラから怒られているのは、上に記載したbuildUserInterfacemydsp関数とmetadatamydsp関数です。こちらはMetaGlue型が解決できないと怒られています。実際にコンパイルしてみると、こうです。

% clang four_op_fm.c
four_op_fm.c:209:20: error: unknown type name 'MetaGlue'
void metadatamydsp(MetaGlue* m) { 
                   ^
four_op_fm.c:367:42: error: unknown type name 'UIGlue'
void buildUserInterfacemydsp(mydsp* dsp, UIGlue* ui_interface) {
                                         ^
2 errors generated.

はい、そうですよね。

computemydsp関数を見る

一番のキモはこの関数でしょう。

void computemydsp(mydsp* dsp, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs) {
	FAUSTFLOAT* input0 = inputs[0];
	FAUSTFLOAT* input1 = inputs[1];
	FAUSTFLOAT* input2 = inputs[2];
	FAUSTFLOAT* input3 = inputs[3];
	FAUSTFLOAT* input4 = inputs[4];
	FAUSTFLOAT* input5 = inputs[5];
	FAUSTFLOAT* input6 = inputs[6];
	FAUSTFLOAT* input7 = inputs[7];
	FAUSTFLOAT* input8 = inputs[8];
	FAUSTFLOAT* output0 = outputs[0];
	float fSlow0 = (float)dsp->fHslider0;
	float fSlow1 = (float)dsp->fHslider1;
...

			output0[i0] = (FAUSTFLOAT)(fSlow0 * ((((((((iSlow2 ? (fSlow3 * (((fTemp2 * cosf(fTemp22)) + (fTemp23 * sinf(fTemp22))) * fTemp24)) : 0.0f) + (iSlow41 ? (fSlow3 * (fTemp24 * ((fTemp2 * cosf(fTemp27)) + (fTemp23 * sinf(fTemp27))))) : 0.0f)) + (iSlow42 ? (fSlow3 * (fTemp24 * ((fTemp2 * cosf(fTemp30)) + (fTemp23 * sinf(fTemp30))))) : 0.0f)) + (iSlow43 ? (fSlow3 * (fTemp24 * ((fTemp2 * cosf(fTemp33)) + (fTemp23 * sinf(fTemp33))))) : 0.0f)) + (iSlow44 ? (fTemp31 + (fSlow3 * (fTemp24 * ((fTemp2 * cosf(fTemp35)) + (fTemp23 * sinf(fTemp35)))))) : 0.0f)) + (iSlow45 ? ((fTemp31 + (fSlow7 * (fTemp21 * ((fTemp5 * fTemp14) + (fTemp16 * fTemp20))))) + (fSlow3 * (fTemp24 * ((fTemp2 * fTemp14) + (fTemp16 * fTemp23))))) : 0.0f)) + (iSlow46 ? ((fTemp31 + (fSlow7 * (fTemp21 * ((fTemp5 * cosf(fTemp36)) + (fTemp20 * sinf(fTemp36)))))) + (fSlow3 * (fTemp24 * ((fTemp2 * cosf(fTemp37)) + (fTemp23 * sinf(fTemp37)))))) : 0.0f)) + (iSlow47 ? (((dsp->fRec4[0] + (fSlow11 * (fTemp17 * ((fTemp8 * cosf(fTemp38)) + (fTemp15 * sinf(fTemp38)))))) + (fSlow7 * (fTemp21 * ((fTemp5 * cosf(fTemp39)) + (fTemp20 * sinf(fTemp39)))))) + (fSlow3 * (fTemp24 * ((fTemp2 * cosf(fTemp40)) + (fTemp23 * sinf(fTemp40)))))) : 0.0f)));
			dsp->fRec1[1] = dsp->fRec1[0];
			dsp->fRec2[1] = dsp->fRec2[0];
			dsp->fRec3[1] = dsp->fRec3[0];
			dsp->fRec5[1] = dsp->fRec5[0];
			dsp->fVec2[1] = dsp->fVec2[0];
			dsp->fRec7[1] = dsp->fRec7[0];
			dsp->iRec8[1] = dsp->iRec8[0];
			dsp->fRec4[1] = dsp->fRec4[0];
		}
	}
}

ザ・自動生成な感じで微笑ましい?ですね。

ざっとコードを眺めていきましょう。

まずは引数から

void computemydsp(mydsp* dsp, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs)

dsp

dspは構造体で定義されたGUIコンポーネントの値ですね。

typedef struct {
	FAUSTFLOAT fHslider0;
	FAUSTFLOAT fHslider1;
	FAUSTFLOAT fVslider0;
	int fSampleRate;
	float fConst0;
	float fConst1;
	FAUSTFLOAT fHslider2;
	FAUSTFLOAT fVslider1;
         •
         •
         •
} mydsp;

このそれぞれの値が何に紐づいているのかはどこで分かるかというと、

void buildUserInterfacemydsp(mydsp* dsp, UIGlue* ui_interface) {
	ui_interface->openVerticalBox(ui_interface->uiInterface, "four_op_fm");
	ui_interface->declare(ui_interface->uiInterface, &dsp->fButton0, "1", "");
	ui_interface->addButton(ui_interface->uiInterface, "gate", &dsp->fButton0);
	ui_interface->declare(ui_interface->uiInterface, &dsp->fHslider2, "2", "");
	ui_interface->addHorizontalSlider(ui_interface->uiInterface, "freq", &dsp->fHslider2, (FAUSTFLOAT)200.0f, (FAUSTFLOAT)40.0f, (FAUSTFLOAT)2000.0f, (FAUSTFLOAT)0.00999999978f);
	ui_interface->declare(ui_interface->uiInterface, &dsp->fHslider0, "3", "");
	ui_interface->addHorizontalSlider(ui_interface->uiInterface, "gain", &dsp->fHslider0, (FAUSTFLOAT)0.5f, (FAUSTFLOAT)0.0f, (FAUSTFLOAT)1.0f, (FAUSTFLOAT)0.00999999978f);
         •
         •
         •
}

buildUserInterfacemydsp関数でちまちま確認するしかないようです…。基本的にはFaust側で定義した順番のようですが。

count

これは、関数中の最後のforループをみると

for (i0 = 0; (i0 < count); i0 = (i0 + 1)) {
         •
         •
	output0[i0] = •••;
         •
         •
}

とあるので、出力する波形のサンプル数でしょう。

inputs / outputs

関数内の頭の方をみると、以下のようなコードが展開されていました。

FAUSTFLOAT* input0 = inputs[0];
FAUSTFLOAT* input1 = inputs[1];
FAUSTFLOAT* input2 = inputs[2];
FAUSTFLOAT* input3 = inputs[3];
FAUSTFLOAT* input4 = inputs[4];
FAUSTFLOAT* input5 = inputs[5];
FAUSTFLOAT* input6 = inputs[6];
FAUSTFLOAT* input7 = inputs[7];
FAUSTFLOAT* input8 = inputs[8];
FAUSTFLOAT* output0 = outputs[0];

おもむろに現れる9つのinput。これ何かと思ったら、以前の投稿で放置していた髭の数と同じでした。

つまり、Faust内で完結しない入力の線の分だけ、上記のようにコード内に展開されるようです。outputsはfaust側でprocess変数にモノラルで突っ込んでいるので1つになっています。

音を合成するときに面倒なので、不要な髭は0をinputに追加するようにして消しちゃうのが良いです。例えば、

//change from
alg(1) = op1,op2:>op3:op4;

//to
alg(1) = op1,(0:op2):>op3:op4;

こうすれば上記の絵の髭がなくなります。

output0

上記で示したように、サンプル数分だけの波形を詰めるのがこの変数。どんな計算をしているかというと

output0[i0] = (FAUSTFLOAT)(fSlow0 * ((((((((iSlow2 ? (fSlow3 * (((fTemp2 * cosf(fTemp22)) + (fTemp23 * sinf(fTemp22))) * fTemp24)) : 0.0f) + (iSlow41 ? (fSlow3 * (fTemp24 * ((fTemp2 * cosf(fTemp27)) + (fTemp23 * sinf(fTemp27))))) : 0.0f)) + (iSlow42 ? (fSlow3 * (fTemp24 * ((fTemp2 * cosf(fTemp30)) + (fTemp23 * sinf(fTemp30))))) : 0.0f)) + (iSlow43 ? (fSlow3 * (fTemp24 * ((fTemp2 * cosf(fTemp33)) + (fTemp23 * sinf(fTemp33))))) : 0.0f)) + (iSlow44 ? (fTemp31 + (fSlow3 * (fTemp24 * ((fTemp2 * cosf(fTemp35)) + (fTemp23 * sinf(fTemp35)))))) : 0.0f)) + (iSlow45 ? ((fTemp31 + (fSlow7 * (fTemp21 * ((fTemp5 * fTemp14) + (fTemp16 * fTemp20))))) + (fSlow3 * (fTemp24 * ((fTemp2 * fTemp14) + (fTemp16 * fTemp23))))) : 0.0f)) + (iSlow46 ? ((fTemp31 + (fSlow7 * (fTemp21 * ((fTemp5 * cosf(fTemp36)) + (fTemp20 * sinf(fTemp36)))))) + (fSlow3 * (fTemp24 * ((fTemp2 * cosf(fTemp37)) + (fTemp23 * sinf(fTemp37)))))) : 0.0f)) + (iSlow47 ? (((dsp->fRec4[0] + (fSlow11 * (fTemp17 * ((fTemp8 * cosf(fTemp38)) + (fTemp15 * sinf(fTemp38)))))) + (fSlow7 * (fTemp21 * ((fTemp5 * cosf(fTemp39)) + (fTemp20 * sinf(fTemp39)))))) + (fSlow3 * (fTemp24 * ((fTemp2 * cosf(fTemp40)) + (fTemp23 * sinf(fTemp40)))))) : 0.0f)));

ええっと…。三項演算子がいっぱいあるのは、もしや以前の投稿で使ったselect2

コロンの数がアルゴリズム数だけあるし、ビンゴですね。「三項演算子っぽい」と表現してましたが、Cにしたらまさに三項演算子になるとは。

先頭に登場するiSlow2 ? はおそらくアルゴリズム0を選択するときにtrueになるのでしょうね、とコードを見てみると、

int iSlow2 = (fSlow1 == 0.0f);

fSlow1はアルゴリズムを切り替えるスライダーの値なので予想は正しかったわけですが、流石にモヤっとしますね…。本来floatを扱うスライダーでアルゴリズムを切り替えているから上記のようなコードになってしまっているのでしょうが、いずれにせよparとselect2の組み合わせはCに持ってきた時にはswitch文とかで書き換えた方がCPUに優しそうです。

他の関数も見てみる

音を出す関数が分かったので、あとは初期化とかの関数を見ておきましょう。

newmydsp

シンプルにmydspを確保してくれます。反対に解放してくれるのはdeletemydspです。中を見るとfreeしてるだけですが。

initmydsp

基本的にこいつを叩けば色んな値が初期化されるようです。以下がdoxygenで描いてみたコールグラフです。

こいつを呼んでおけば色々やってくれそうですね。

せっかくなので音を出してみる

せっかくなので、これらのコードを使って音を出してみたいと思います。

ゴール

シンプルに「ド レ ミ ファ ソ ラ シ ド」と一オクターブ音を出してみます。途中でパラメータもいじってみましょう。

出力されたソースコードの修正

まずはコンパイルエラーになるmetadatamydspbuildUserInterfacemydspの2つの関数をさくっとコメントアウトします。

次にこれらのコードを呼び出す部分を別ファイルで用意したいので、ヘッダファイルを作っておきます。その時、mydsp構造体もそちらに移植しておきます。

#ifdef __cplusplus
extern "C" {
#endif

#ifndef FAUSTFLOAT
#define FAUSTFLOAT float
#endif 

typedef struct {
	FAUSTFLOAT fHslider0;
	FAUSTFLOAT fHslider1;
	FAUSTFLOAT fVslider0;
	int fSampleRate;
	float fConst0;
	float fConst1;
	 •
	 •
	 •
} mydsp;

mydsp* newmydsp();
void deletemydsp(mydsp* dsp);
int getSampleRatemydsp(mydsp* dsp);
int getNumInputsmydsp(mydsp* dsp);
int getNumOutputsmydsp(mydsp* dsp);
void initmydsp(mydsp* dsp, int sample_rate);
void computemydsp(mydsp* dsp, int count, FAUSTFLOAT** inputs, FAUSTFLOAT** outputs);

#ifdef __cplusplus
}
#endif

computemydspを叩くコードを書く

#include <vector>
#include "four_op_fm.h"
#include "wav_exporter.hpp"
#define SAMPLE_RATE 48000

int main(){
    float scale[] = {261.626f, 293.665f, 329.628f, 349.228f, 391.995f, 440.000f, 493.883f, 523.251f};

    mydsp* dsp = newmydsp();
    initmydsp(dsp, SAMPLE_RATE);

    std::vector<double> vector;
    float* buffers[1];
    float wav_buffer[SAMPLE_RATE];
    buffers[0] = wav_buffer;

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

        //sound on for 1 sec
        dsp->fHslider2 = scale[i];
        dsp->fButton0 = 1.0f; //gate on
        dsp->fHslider1 = i%8; //change algorithm
        dsp->fHslider3 = i%3 + 2; //op1 feedback level
        dsp->fVslider7 = i; //op1 freq index
        computemydsp(dsp, SAMPLE_RATE, 0, buffers);        
        for (int i = 0; i < SAMPLE_RATE; i++){
            vector.push_back(wav_buffer[i]);
        }

        //sound off for 0.5 sec
        dsp->fButton0 = 0.0f; //gate on
        computemydsp(dsp, SAMPLE_RATE / 2, 0, buffers);
        for (int i = 0; i < SAMPLE_RATE / 2; i++){
            vector.push_back(wav_buffer[i]);
        }
    }
    write_wav("test.wav", SAMPLE_RATE, false, vector);

    deletemydsp(dsp);
    return 0;
}

なお、write_wav関数は、CUI版のs98playerを実装するときに利用したymfmのサンプルコードから拝借(およびちょい修正)したものです。

scale配列にC4からC5までの周波数が入っているので、forループで1秒発音・0.5秒消音を1音ずつ音階を変えながら全体の波形データの配列に突っ込んでいっています。ここはもっとシンプルにできそうですが、ちょっとfloat -> doubleにする必要があったのでここも適当にforループで…。

1音ごとにアルゴリズムやフィードバックレベルなどを変更していますので、音色が変わっていくことにも期待です。

出力された音は…?

こんな感じになりました(元々はwavですが、webでプレイバックできるようにmp3に変換しています)。

正しく出力されてるようです。めでたい。

まとめ

faustからCコードとして出力したものを利用して、音を出すことができました。

以前やったようにJUCEのプロジェクトファイルとして出力するのでも良いのですが、何か手直ししたりする場合には出力されるコード自体がどういうものかわかっておくと何かと役に立つと思います。

今回はIDEから直接出力しましたが、faustのコマンドラインでは様々なオプションを使ってコードに出力できるようです。具体的には、

  • 浮動小数点をfloatではなくdoubleにする
  • CPUのSIMD命令を使って高速に計算できるようにclang向けにヒントとなるpragmaを挟む

などなど。この辺りも深くみていくと面白そうです。