hvccを使って、Pure Dataからlogue SDK用のCファイルを生成してみていますが、生成したコードをlogue SDKから利用するためには追加で手作業が必要となっています。
この手作業の部分を自動化するのに、hvccのexternal generatorを作成中です。external generatorはhvccのプラグインのようなもので、これをhvccから呼び出して、hvccが生成するコードをカスタマイズすることができます。いくつかのプラットフォーム(DaisyやEmscriptenなど)向けのgeneratorは最初から用意されています。
今回はとりあえずexternal generatorの中身を調べて、できそうなところから手を付け始めましたので、途中経過として記録しておきます。
外部ジェネレータの目的
logue SDK用のexternal generatorができれば、こちらの記事で手作業でやっていたことが自動化できます。具体的には以下のような作業です。
■ビルドに必要なファイルのコピー
・Makefile
・ld/配下のosc_api.syms
、rules.ld
、userosc.ld
・tpl/_unit.c
■hvccが生成したファイルの編集
・HvUtils.hでhv_alloc、hv_realloc、hv_freeの定義を追加
■テンプレートからのファイル作成
・hvccコンテキストのlogue SDK向けラッパ
初期化、音声信号の生成、パラメータ設定(*)に関するコード
ヒープとして用いるスタティック変数のサイズ設定(*)
・project.mk
プロジェクト名
コンパイルするソースファイルのリスト
・manifest.json
オシレータ名
パラメータやノートイベントに関する設定(*)
上の作業の中には単にファイルをコピーするような簡単なものもありますが、(*)を付けたものは手間がかかりそうです。
外部ジェネレータのしくみ
external generatorは、hvccのcompiler.pyのcompile_dataflowから呼び出されます。呼び出された時点で、hvccによるCのコードの生成は済んでいます。external generatorは、生成されたコードや呼び出し元から与えられる情報を元に、必要な処理を行っていきます。
呼び出し元から与えられる情報ですが、external generatorは以下のような関数で呼び出されますので、これらのパラメータの値がexternal generatorに与えられる情報ということになります。
def compile(
cls,
c_src_dir: str,
out_dir: str,
externs: ExternInfo,
patch_name: Optional[str] = None,
patch_meta: Meta = Meta(),
num_input_channels: int = 0,
num_output_channels: int = 0,
copyright: Optional[str] = None,
verbose: Optional[bool] = False
)
コードをみたところ、概略以下のような感じのようです。
(cls:クラスオブジェクト)
c_src_dir: 生成したCのソースファイルのディレクトリ
out_dir: -oオプションで指定された出力先ディレクトリ
externs: Pure Dataパッチ内で、パッチ外から与えられると宣言されたパラメータに関する情報 ※
patch_name: -nオプションで指定されたパッチ名称
patch_meta: -mオプションで与えられたメタデータ(現状、特定のプラットフォームでのみ有効)
num_input_channels: 入力チャネル数
num_output_channels: 出力チャネル数
copyright: –copyrightオプションで与えられた著作権表示の文字列
verbose: -vオプションの値
※@hv_param、@hv_event、@hv_tableなどで宣言する。
外部ジェネレータの中身
外部ジェネレータは上記のAPIで呼び出されるPythonの関数です。Pythonでできることは何でもできますが、特にlogue SDK対応としては、externsに含まれる情報とc_src_dirに入っているソースコードが、外部ジェネレータが扱う主な素材になります。
externsに含まれる重要な情報は、Pure Dataのreceiveオブジェクトで受け取るパラメータの情報で、これはexterns.parameters.inParam
で参照できます。inParamの中身は、例えばこんな感じになっています。
[ ( 'alt',
IRReceiver(display='alt', hash='0xEF06E136', extern='param', attributes={'default': 0.0, 'max': 0.5, 'min': 0.0, 'type': 'float'}, ids=['Db7fCjel'])),
( 'harm1',
IRReceiver(display='harm1', hash='0xF115A43D', extern='param', attributes={'default': 2.0, 'max': 8.0, 'min': 1.0, 'type': 'float'}, ids=['1IauOKrI'])),
( 'harm2',
IRReceiver(display='harm2', hash='0x44556F7B', extern='param', attributes={'default': 4.0, 'max': 8.0, 'min': 1.0, 'type': 'float'}, ids=['v2kUWZnX'])),
( 'harm3',
IRReceiver(display='harm3', hash='0xEC1D0E7B', extern='param', attributes={'default': 8.0, 'max': 8.0, 'min': 1.0, 'type': 'float'}, ids=['i3mK0BaS'])),
( 'pitch',
IRReceiver(display='pitch', hash='0x8B2148DD', extern='param', attributes={'default': 0.5, 'max': 1.0, 'min': 0.0, 'type': 'float'}, ids=['7gl8w2LT'])),
( 'shape',
IRReceiver(display='shape', hash='0xB063C1EC', extern='param', attributes={'default': 0.5, 'max': 1.0, 'min': 0.1, 'type': 'float'}, ids=['bJV5XG2e']))]
パラメータ名、値域、デフォルト値などの情報はreceiveオブジェクトで指定したものがそのまま入っています。ちなみにexternsを生成しているのはgenerate_extern_infoです。
このPure Data側のパラメータの情報は、logue SDKで入力できるパラメータに適切にマッピングする必要があります。logue SDKではパラメータの値域の制約(ShapeとAltは0~1023、一般のパラメータは-100~100)があるためです。なお、Pure Dataでは数値は基本的にfloatしかありません。
また、Pure Data側のreceiveオブジェクトと、
・logue SDKの8つのパラメータ(Shape、ShiftShape、6つのパラメータ)
・実行時に取得できるパラメータ(オシレータピッチおよびLFO)
・イベント(ノートON、ノートOFF)
等とのマッピングも考える必要があります。このあたりはちょっと設計に頭をひねるところですね。
ほとんどのパラメータは名称を固定で決めてしまえば済みますが、logue SDKでmanifest.jsonで指定する6つのパラメータの名称や、どのパラメータの何番目にするかなどは、ユーザが自由に決められるようにしたいところです。本当はメタデータが使えるといいのですが、外部ジェネレータから使う方法が分かりません。
外部ジェネレータの使い方
external generatorは、hvccを利用する際に以下のように呼び出します。
$ hvcc patch.pd -G example_hvcc_generator
カレントディレクトリにexample_hvcc_generator.py
というexternal generatorが置かれている想定です。(external generatorはPythonで書きます。hvcc自体もPythonで書かれています。)
なお、上の例ではカレントディレクトリがPythonのサーチパスに含まれている想定です。
含まれていない場合は
export PYTHONPATH=$PYTHONPATH:$(pwd)
のように追加する必要があります。
現時点の進捗
現状、上に挙げたTo Doリストの中では、パラメータに関するもろもろの課題と、ヒープのサイズ設定の課題がまだ未実装ですが、それ以外は一通り動くようなジェネレータができています。下図は実験用のPure Dataパッチです。
ありがちなadditive synthesizerです。基音の他、2・4・8・1/2倍の周波数の音をミックスするようなオシレータになっています。
これをジェネレータにかけると、必要なファイルが生成され、make installでNTS-1にロード可能なオシレータユニットがビルドできます。
できたオシレータユニットを動かしている様子です。音が割れてしまっているところがありますが・・・。
[WIP] 開発中のlogue SDK用external generator。Pure Dataパッチ -> hvcc -> logue SDK用external generator -> make install でNTS-1用オシレータユニットを作れるようにしています。 pic.twitter.com/kDwr4zR1Db
— boochowp (@boochowp) February 24, 2025
気になるバイナリのサイズはこんな感じです。
$ size build/sinx4.elf
text data bss dec hex filename
20200 2128 3136 25464 6378 build/sinx4.elf
この倍くらいまでの複雑さのパッチなら、何とかなりそうです。
コメント