Pure Dataのパッチをlibpdを使ってDrumlogueで動かしてみた

1月頃に、こちらの記事で「Drumlogueならlibpdを動作させられるかもしれない」と書きました。それを今回、最低限ですが一応動作させることが実証できましたので、記事にしておきます。

動作させたパッチは、以前こちらの記事で使ったものと同じで、オシレータそのものは[osc~]オブジェクトで、MIDIノートイベントを受信してサイン波を鳴らします。また、簡易的なエンベロープがついています。

動かしている様子です。

パッチそのものはこちらです。特に改変する必要はなく、そのままdrumlogueで動かすことができます。

コードは以下のリポジトリで公開しています。

GitHub - boochow/libpd_drumlogue_test_synth: test build of libpd for KORG drumlogue with logue SDK
test build of libpd for KORG drumlogue with logue SDK - boochow/libpd_drumlogue_test_synth

logue SDK v1でPure Dataを動作させた際は、hvccを使ってPure DataのパッチをいったんCのコードに変換し、それをlogue SDKと一緒にコンパイルしていました。ただ、hvccで利用できるPure Dataのオブジェクトは、全種類ではなくサブセットでした。

今回は、libpdを使ってフルセットのPure Dataをdrumlogue上で動かします。PCで動作させるのと比べれば制約はありますが、hvccを使う場合よりは互換性が高いはずです。

パッチをファイルで与えるには

libpdをdrumlogueで動かす上での最大の課題は、ファイルシステムが使えないことでした。

drumlogue自体はLinux系のOSで動いているように思われますし、CPUはArmV7系で、RAMも十分にあるので、ビルドして動かすこと自体には特段問題はありません。

しかし、libpdはパッチを開くときにシステムコールopenを使います。そのため、パッチのファイルパスを与える必要があります。

logue SDKのユニットは単一の実行ファイルで、パッチを別ファイルとして切り離して持つことはできません。そこで、採りうる方法としては、動かしたいパッチをいったんデータとしてユニット内に保存し、実行時にファイル化する(ファイルを作成してパッチのデータをそこへ書き出す)ことが考えられます。

ファイルは一時的に使うだけなので、RAMディスクが良いと考えられます。システムに影響を与えずにアプリケーション単体で利用できる方法として、memfd_create()を使用しました。

memfd_create()はLinuxにおいて無名(anonymous)ファイルを作る機能で、本来はファイル名を持たないのですが、/proc/self/fd/NUMというパスでアクセスすることも可能です(NUMはmemfd_create()の戻り値として与えられる整数)。

libpd側でファイルを開く関数はlibpd_openfile(fname, dir)です。ファイルパスではなくファイル名とディレクトリに分けて与えることになっています。ただ、コードを追っていくと

libpd_openfile() => glob_evalfile() => binbuf_evalfile() => binbuf_read()

と呼び出されて、結局dirとfnameは連結されてパス名としてsys_open()に渡されます。あとは問題なくパッチを実行することができました。

libpdにおける音声入出力

libpdの音声の入出力は、floatの配列への読み書きが基本になります。複数チャネルの場合は、インターリーブ(LRLR・・・)を行います。バリエーションとして、double型、short型など異なる型の配列、インターリーブ無しの読み書きなども可能なようです(試していませんが)。

なお、libpdのAPIに関するドキュメントはこちらにあります。

libpd
Pure Data embeddable audio synthesis library. Contribute to libpd/libpd development by creating an account on GitHub.

一度に書き込むサンプルの数ですが、blocksize単位で行います。blocksizelibpd_blocksize()で取得できます。blocksizeのデフォルトは64で、これはDEFDACBLKSIZEとして内部で定義されています。

音声入出力バッファのサイズはblocksize×チャネル数で、drumlogueの場合はチャネル数は2で固定です。

バッファを埋める関数は以下のような単純なものになります。Synthユニットの場合ですので、入力バッファはlogue SDK側にはありません。(drumlogueでは、framesは現状64になります。blocksize_の値はユニットの初期化時に設定しています。)

    fast_inline void Render(float * out, size_t frames) {
        static float input_buffer[1024];

        memset(input_buffer, 0, sizeof(input_buffer));
        while(frames > 0) {
            libpd_process_float(1, input_buffer, out);
            frames -= blocksize_;
        }
    }

libpdにおけるMIDIイベント

外部からMIDIノートイベントをlibpdに与えて、[notein]オブジェクトの動作に反映させることができます。同様にピッチベンド、チャンネルプレッシャー、アフタータッチについてもそれぞれPure Dataパッチへ送信するための関数が用意されています。

logue SDKでの実装は以下のようになります。

    inline void NoteOn(uint8_t note, uint8_t velocity) {
        libpd_noteon(0, note, velocity);
    }

    inline void NoteOff(uint8_t note) {
        libpd_noteon(0, note, 0);
    }

    inline void GateOn(uint8_t velocity) {
        libpd_noteon(0, note_, 127);
    }

    inline void GateOff() {
        libpd_noteon(0, note_, 0);
    }

    inline void AllNoteOff() {}

    inline void PitchBend(uint16_t bend) {
        libpd_pitchbend(0, bend);
    }

    inline void ChannelPressure(uint8_t pressure) {
        libpd_aftertouch(0, pressure);
    }

    inline void Aftertouch(uint8_t note, uint8_t aftertouch) {
        libpd_polyaftertouch(0, note, aftertouch);
    }

AllNoteOffは専用の関数が用意されていないので、16個のMIDIチャネルに対してCCでAllNoteOffを送るか、また全MIDIチャネルの全ノートナンバーにNoteOffを送ることで実現できますが、CCで送信する場合はPure Dataパッチ側にこのCCをハンドリングする機能が必要です。

パラメータの値を設定する

上記のリポジトリのコードでは使っていませんが、たとえば[osc~]に直接周波数を与えるには

libpd_float("myvar", 440.0f);

のようにしてPure Dataの[send myvar]オブジェクト相当の動作を行わせることができます。もちろんメッセージやシンボルも送信可能です。

ただ、そもそもどんな変数名で待ち受けが行われているのか、知るためのAPIはありません。hvccの場合は、Pure Dataパッチを解析した結果を外部ジェネレータで受け取ることができましたが、libpdはPure Dataを実行することに特化していて、パッチの中身を解析するようなAPIがありません。かろうじて、arrayオブジェクトの中身を読み書きする機能がある程度です。

drumlogueのノブでPure Dataパッチのパラメータの値を変更するには、少なくともパラメータの名前と値域を知る必要があります。そのためには、

①あらかじめパラメータの名前や値域を決めておき、パッチ作成時にその名前を使う
②Pure Dataパッチの解析を行うツールを作る

のいずれかが必要になりそうです。

Pure Dataのパッチファイルはテキスト形式で行志向なので、解析自体は比較的やりやすい形式ではありますが、ここは今後の課題です。

abstractionが使えない

Pure Dataのパッチは、複数のファイルに分割することができます。これはabstractionと呼ばれていて、パッチの入ったファイルの名称を別のファイルから(オブジェクトの名称として)参照することができます。しかし、今回使ったmemcreate_fdを使う方法では、ファイル名を情報として利用することができません。従ってabstractionを使って複数のファイルに分けられたパッチは、そのまま動作させることが不可能です。

このような場合、複数のファイルの内容を1つのファイルにマージすることが考えられます。おそらくabstractionをsubpatchに変換するツールを作ることは可能そうな気はしますが、これも今後の課題です。

wavファイルがロードできない

Pure Dataパッチは外部の音声ファイルをロードできますが、これもファイルシステムを持たないdrumlogueでは動作ができません。drumlogueではWavファイルをユーザがアップロードすることは可能ですが、logue SDKからはそのファイルはファイルとしてではなく、メモリ上にロードしたデータへのポインタとして扱います。

libpdではパッチ内のarrayを参照可能なので、ロードしたWavデータをarrayに書き込むことは可能そうですが、ロードするWavデータやロードするタイミングをどのようにしてPure Dataパッチから指定するのか、インタフェースを検討する必要があります。

というわけで課題はいろいろあるのですが、既存のパッチを動かすのでなく、最初からdrumlogue向けにパッチを作成するのであれば、いろいろなことができそうな気はします。今回作成したのはSynthユニットでしたが、Delay/Reverbについてもそのうち試してみようと思います。

コメント