前回の続きです。
logue SDK 2.0の勉強のために、とりあえず簡単なディレイを作ってみました。
以前、VeryShortという超短時間のディレイを作りましたが、その移植版です。
very short delayのdrumlogue移植版できた。logue SDK 2.0だいたい理解した pic.twitter.com/Hq52dczgVK
— boochowp (@boochowp) March 21, 2023
logue SDK 2.0で一番簡単そうなのは、入力信号をちょっといじって出力にコピーすれば済むディレイ系エフェクタのように思います。それが今回ディレイを題材に選んだ理由です。
SDK 1.0は、オシレータの開発が簡単に行えるのが魅力でした。
しかしSDK 2.0では、オシレータではなくシンセサイザとなり、若干ハードルが上がっています。
drumlogueでは外部にフィルタやエンベロープ、LFOなどが無いので、シンセサイザユニットは(実用上は)それらも含めて実装する必要があるためです。
SDK2系のファイル構成
まずはディレイのダミーユニットをコピーします。
user@logue-sdk:~$ cd drumlogue/
user@logue-sdk:~/drumlogue$ cp -r dummy-delfx/ veryshort
コピーしたダミーのユニットには、以下の5つのファイルが含まれています。
user@logue-sdk:~/drumlogue$ wc veryshort/*
435 1174 10194 veryshort/Makefile
47 68 807 veryshort/config.mk
154 519 5001 veryshort/delay.h
69 507 3779 veryshort/header.c
87 269 2519 veryshort/unit.cc
792 2537 22300 total
1つずつ見ていきます。
Makefileおよびconfig.mk
Makefileはいじる必要がありません。config.mkにMakefileで使用するパラメータが定義されているからです。config.mkは、内容的にはSDK1.0におけるproject.mkとほぼ同じです。
プロジェクト名はconfig.mkで変更します。
delay.h
delay.hにはDelayクラスが定義されています。
新しく作成するユニットが行う処理は、このファイルの中に書くか、unit.ccの中で書きます。
後述しますがunit.ccはwrapperのように見えるので、delay.hの中に実装を書くのが良いように思います。
header.c
header.cにはパラメータに関する情報を記載します。SDK1.0のmanifest.jsonに相当する部分です。SDK1.0に比べ、記述できる情報がかなり増えました。
必須の情報は以下のものです。
.name : ユニットの名前
.num_presets : ユニットが公開するプリセットの数
.num_params : ユニットが公開するパラメータの数(最大24)
.params : パラメータ記述子の配列
SDK2.0ではプリセットをユニットの中に含めることができるようになりました。
また、利用できるパラメータの数が6→24と大幅に増えました。これは有難いです。
Delayのパラメータは、SDK1.0ではディレイはdepthとtimeに固定されていましたが、SDK2.0ではユニット種別に関わらず24個までのパラメータを自由に定義できるようです。
パラメータの種類も増えました。
従来は整数とパーセンテージの2種類でしたが、SDK2.0では19種類に増え、固定/浮動小数も扱えるようになりました。個々のパラメータの最大値・最小値・デフォルト値・小数点の扱いなども指定することができます。
unit.cc
unit.ccには、APIがユニットを呼び出す際のエントリポイントが実装されています。内容は、初期化の際にDelayクラスのインスタンスを初期化し、それ以外の処理はすべてそのインスタンスのメソッドを呼び出すというものです。
つまり、logue SDKのAPIからDelayクラスを呼び出すためのwrapperがunit.ccということになります。
ですので、デベロッパとしてはDelayを実装したければDelayクラスを拡張すれば良く、logue SDKのAPIコールはunit.ccに任せてもよさそうです。
とはいえ、SDK2のAPIをSDK1と比較しながら一通り見てみます。
音声データの処理
最も重要な、入力の音声データを読み込みつつ出力の音声データを書き出す処理を行う関数は、SDK1.0では
void DELFX_PROCESS(float *main_xn, uint32_t frames)
という関数でしたが、SDK2.0では
__unit_callback void unit_render(const float * in, float * out, uint32_t frames)
という関数に変わります。基本的にはサンプル数がframesで与えられ、サンプルがfloatの配列ということで、あまり大きな変化は無さそうです。
パラメータ設定周り
パラメータの処理は以下のようになります(上:SDK1.0、下:SDK2.0)
void DELFX_PARAM(uint8_t index, int32_t value)
__unit_callback void unit_set_param_value(uint8_t index, int32_t value)
これもあまり大きな変化は無さそうです。ただし、SDK2.0では、アプリケーション側が保持しているパラメータの値をシステム側に渡す以下の関数も実装が必須です。
__unit_callback int32_t unit_get_param_value(uint8_t index)
このほか、パラメータの値を文字列やアイコンで表示させることもできるらしく、それ用の関数が用意されています。
__unit_callback const char * unit_get_param_str_value(uint8_t index, int32_t value)
__unit_callback const uint8_t * unit_get_param_bmp_value(uint8_t index, int32_t value)
ユニットの初期化など
初期化は以下のようになります。
void DELFX_INIT(uint32_t platform, uint32_t api)
__unit_callback int8_t unit_init(const unit_runtime_desc_t * desc)
unit_runtime_desc_t にはサンプリングレートやチャネル数などの情報が入っています。
この他、状態遷移対応として新たに以下のコールバック関数が用意されました。
終了時:
__unit_callback void unit_teardown()
リセット処理:
__unit_callback void unit_reset()
サスペンド時:
__unit_callback void unit_suspend()
レジューム処理:
__unit_callback void unit_resume()
オリジナルのディレイを実装する
というわけで構造が大体わかったので、VeryShortの移植を実装してみました。
コードはlogue SDK 1.0系とは全く異なるのですが、処理内容はほぼ同じ、ということでVeryShortのリポジトリに別ブランチを切って、そこに置いてあります。
本体はdelay.hに入れています。
logue SDK 1.0ではディレイラインの実装がライブラリとして提供されていましたが、logue SDK 2.0にはそのようなサポートが無いようです。(1.0のファイルをコピーして使えばよいのかもしれませんが。)
ライブラリが提供されていないのは、きっとNEONに最適化したライブラリを提供すべく中の人が頑張っているのでしょう!
私はNEONを使ったことがなかったので、今回見よう見まねで使ってみました。
float(32bit)×2つを一度に処理(ロード、ストア、加算、定数倍)できるようです。
あと、SDK1と異なるのはパラメータの取り扱いでしょうか。
詳しくは別記事にしようと思いますが、今回は2つのパラメータを
・Timeは0.5ms~500.0msまで、0.1ms刻み
・Depthは-99%から+99%まで、0.5%刻み
という形式で使っています。このような細かい指定はSDK1ではできませんでした。
また、APIとしてはパラメータが変更されたときに呼ばれるsetParameterと、逆にディスプレイに表示すべき現在のパラメータ値を取得する必要があるときに呼ばれるgetParameterValueの2つがあります。
このとき、setParameterで渡された値とgetParameterValueで返す値は基本的に一致したほうが良いようです。
まとめ
とりあえずごく簡単なディレイをlogue SDK 2.0で作成してみました。
感想をまとめると、logue SDK 2.0では
・API以外のライブラリが(現時点では)ほとんど無い
・パラメータの種類が増え、設定だけでなく読み出しのAPIが追加された
・状態遷移のAPIが増えた
というのが大きな違いかなと思います。
ソースコードだけ見ると大幅に変わったように見えるかもしれませんが、ダミーのコードが提供されているので、実際に書かなければならないコードだけで見るとそれほど大きくは変化していません。
今後BiQuadフィルタなど、SDK1で提供されていた各種ライブラリがNEONに最適化されて提供されるといいなと思います。
コメント
ARMv7のNEONは128bit幅のはずです。floatならx4です。
Intel® ISPC User’s Guideをチェックすると、targetとしてneon-i32x8もあるようです(最新のものは詳しくないので、基本128bitだと思っています)。
https://ispc.github.io/ispc.html
ISPCはスケーラブルに並列化したい場合にはお勧めです。ご参考までに。
ありがとうございます。多分、元のコードがL・Rの2チャンネルを同時に処理するためのコードだったからだと思います。4つ並列処理できればモノラルのシンセを4音ポリにするのに便利そうですね。
私もdrumlogueのソースをチェックしている時に、ステレオで並列化しているコードを見かけました。
おっしゃる通りにポリ数で並列化するのも良いし、部分音や時間依存がなければサンプル数でも並列化は可能で、いろいろと考えられるのが面白いところです。