前回に引き続き、Pure Data→logue SDK変換の話です。NTS-1ではある程度安定して動くようになってきたので、prologueでの動作を試してみました。
動作確認に使ったのはこの記事で使用したサイン波×4+1のオシレータです。
基本的には動作したのですが、問題が発生しました。prologueでパラメータの変更を行うと、取りこぼしや異常動作が起きてしまいます。Shapeノブが使えないのでは、実用に耐えません。パラメータ設定の処理が重すぎるのが原因であることは容易に想像がつくので、内部構成を見直しました。
まず、パラメータ値のhvccコンテキストへの反映タイミングです。
これまでは下図のように、パラメータが変更されるとその都度、hvccコンテキストにメッセージを送ってパラメータを変更していました。
この方法はNTS-1では動作していたのですが、prologueには重すぎたようです。
もともと、prologueのMPU(STM32F402)はNTS-1のMPU(STM32F446)よりも非力なのと、prologueのほうが波形のレンダリングの頻度(OSC_CYCLEがコールされる頻度)が高いために、パラメータ変更に許容される処理時間がより短いのだと思われます。
なので、下図のようにパラメータ変更の際はフラグを立てるだけにして、波形のレンダリング時にフラグを見てパラメータを設定するようにしました。同様に、ノートオン/ノートオフについても、OSC_NOTEONやOSC_NOTEOFFコールの中ではフラグを立てるだけにしました。
ちなみにOSC_CYCLEがコールされる頻度は、NTS-1は64サンプル、prologueは16サンプルです。これは、次のようなコードを実機で動作させて出音の周波数を見ると判ります。このコードではOSC_CYCLEのたびに波形出力を反転させているので、出音の周波数はOSC_CYCLEの呼び出し周波数の1/2になります。実際に動作させるとNTS-1では375Hz、prologueでは1500Hzになりましたので、OSC_CYCLEの頻度はこの2倍でそれぞれ750Hzと3000Hzです。prologueでは0.3msec程度でOSC_CYCLEの処理を終わらせる必要があることになります。
上記の見直しの結果、パラメータ設定の取りこぼしは無くなりましたが、逆にOSC_CYCLEの処理が重くなり、処理落ちが発生するようになりました。
そのため、まずはレンダリングとパラメータ設定を一度に処理しないように
・OSC_CYCLEの偶数回目のコールでは、パラメータの処理は行わず、要求サイズの2倍の量の波形をレンダリングし、そのうち前半部分を出力する
・OSC_CYCLEの奇数回目のコールでは、パラメータ値をhvccコンテキストへ反映させ、レンダリングは行わず、前回行ったレンダリングの後半部分を出力する
という方法を試してみました。しかし、2倍の量をレンダリングするのは処理が重すぎるようで、性能に余裕があるはずのNTS-1でも処理落ちが起きてしまいました。
レンダリングにあたっては、少なくとも
・オシレータの周波数
・LFOの値
の2つは毎回hvccコンテキストに与える必要があります。その上で、今回のような比較的シンプルなパッチでも処理落ちが生じるのであれば、やはり絶対的な処理能力が不足しているということになります。
これはもう仕方が無いので、音質は下がってしまいますが、サンプリング周波数を1/2にしてレンダリングの負荷を1/2にすることにしました。具体的には、レンダリングの前にhvccコンテキストに与えるオシレータの周波数を2倍にし、一方でレンダリングするサンプル数を1/2にします。得られた波形データを、補間により2倍のサンプル数に増やします。
これで何とか、prologueでも動作するようになりました。実際にはまだ少々問題があって、8音の同時発音のうち、いくつかでパラメータの変更を反映しきれない場合があったりします。この原因はもうちょっと調べる必要があります。
Pure dataからKORG logue SDKに変換したオシレータ。サイン波のWave folderです。サンプリングレートを1/2にしなければなりませんでしたが、prologueでも一応動作するようになりました。パッチの複雑さにもよると思いますが、ノブの操作やLFOにも追従しています。 pic.twitter.com/f1JtTXGuZM
— boochowp (@boochowp) March 16, 2025
また、今週はもう1つの改善点として、必要となるヒープメモリサイズを自動設定するようにしました。これは少しばかり力技なのですが、以前こちらの記事で書いた内容を、Makefileを使って自動化したものです。
分解して書くと、以下のような仕組みになっています。
・hvccで生成したコードおよびlogue SDK用の作ったmallocの差し替え版を利用した、Linux上で動作するコマンドをビルドする
・このコマンドを実行する。コマンドはhvccコンテキストが消費したヒープメモリ(mallocコールの引数の合計値)を出力する
・この出力を使って、以下のような1行だけのファイルを作る(2688はコマンドの出力)
HEAP_SIZE := 2688
・この1行のファイルを、オシレータユニットのMakefile(project.mk)からインクルードする
・オシレータユニットのビルド時のコンパイラオプションに、-DUNIT_HEAP_SIZE=$(HEAP_SIZE)
を与える
・logue SDK用の作ったmallocの差し替え版の中で、UNIT_HEAP_SIZEをヒープ領域のサイズとして利用する
以上です。だいぶややこしいですね。
ややこしいだけでなく、Makefileにしなければならないので、ChatGPTに質問しながら作りました。自力だけでやったら、数倍以上の時間がかかったと思います。
なおこの機能は、logue SDKのビルド環境にネイティブの(ARM用ではない)gccとg++を必要とします。そのため、Docker版のlogue SDKビルド環境では動作しません。その場合はデフォルト値としてヒープに3KB(3072バイト)を割り当てるようにしました。
まあ、ここまできたらユーザがビルド環境を用意するのではなく、私がサーバを立てて、Pure Dataパッチをアップロードしたらlogue SDKでビルドしたオシレータが返ってくるようなオンラインコンパイラが作りたいですね。hvccのドキュメントによると、「MOD platform」と「OWL Plugin」にはそのようなオンラインコンパイラが用意されているようです。
コメント