NTS-1からNTS-1 mk2への移植ガイド(FX編)

これまでに作ってきた、NTS-1で動作するエフェクタ類のいくつかをNTS-1 mkIIに移植してみました。

自分の作成したものだけなので観測範囲は狭いのですが、移植の手順、ポイントみたいなものをメモしておきます。

ファイル構成

新旧を比較すると、主な構成ファイルは以下のように異なります。

内容 NTS-1 NTS-1 mk2
メイン 任意(.c, .cpp) 任意(.c, .cc)
ビルドオプション project.mk config.mk
パラメータ設定等 manifest.json header.c
ビルドスクリプト Makefile Makefile
リンカスクリプト ld/ 不要
スタブ tpl/_unit.c 不要

いずれも記述内容に共通性があるというだけで、ファイルそのものには互換性が無いので、すべて作り直しが必要です。
そんなわけで、まず移植の初手で以下を実行します(これはModFxの例です。他のFXでは、コピー元のダミーのディレクトリ名を変更してください)。C++についてはMakefileの中で指定されている拡張子が異なる(旧SDKはcpp、新SDKはcc)ので、すべてリネームしておきます。

git switch -c nts1mkii
git rm -r manifest.json Makefile project.mk ld tpl
cp ../dummy-modfx/Makefile ../dummy-modfx/config.mk ../dummy-modfx/header.c .
git mv your-main-src-code.cpp your-main-src-code.cc

config.mkの修正

config.mkはプロジェクト名を変更します。
また、

UCXXSRC = unit.cc

となっていますが、unit.ccを自分のメインのソースコードのファイル名に変更します。

header.cの修正

これは旧SDKよりも記述内容が増えていますので、新規のつもりで作成します。デベロッパIDは、自分のプラグインを配布するつもりなら、あったほうがいいです。旧SDKではプラグインを識別する方法がなかったのですが、新SDKではデベロッパIDとユニットIDの組でプラグインを識別できるからです。デベロッパIDを登録する場合はこちらのファイルに対してプルリクエストで要求してください。

パラメータの個数は、増やさずにそのまま移植する場合、モジュレーションFXでは2個,ディレイとリバーブでは3個です。値域はとりあえずデフォルトのまま(0~1023)にしておきます。

ソースコードの変更

ソースコードについてはAPIの変更に対応して何か所か変更する必要があります。以下、主なものを挙げていきます。

インクルードファイルのファイル名の変更

旧:usermodfx.h userdelfx.h userrevfx.h
新:unit_modfx.h unit_delfx.h unit_revfx.h
旧:biquad.hpp  delayline.hpp  simplelfo.hpp
新:dsp/biquad.hpp  dsp/delayline.hpp  dsp/simplelfo.hpp
旧:buffer_ops.h  cortexm.h  fixed_math.h  float_math.h  int_math.h
新:utils/buffer_ops.h  utils/cortexm.h  utils/fixed_math.h  utils/float_math.h  utils/int_math.h

定数およびスタティック変数宣言

最低限必要なのは以下の2つです。
・パラメータ指定
モジュレーションFxは2つ、ディレイFxとリバーブFxは3つの値の定義が必要です。#defineじゃなくconstexprとかenumを使う方法もありますし、シンボル名も変更OKですが、そのあたりはお好みで。以下はモジュレーションFxの例です。(シンボル名がエフェクト毎に異なるのでご注意ください。)

#define k_user_modfx_param_time 0
#define k_user_modfx_param_depth 1

・ランタイムデスクリプション
初期化時に渡される構造体をスタティック変数に保持します。FX系では、実際に使用する機会はあまりなく、唯一の用途はSDRAMの領域確保・解放くらいかもしれません。

static unit_runtime_desc_t s_desc;

初期化関数(FX_INIT)

関数名、パラメータが変わっているので変更します。渡されたパラメータdescは、上で宣言したスタティック変数にコピーしておきます。

旧:
void MODFX_INIT(uint32_t platform, uint32_t api)
void DELFX_INIT(uint32_t platform, uint32_t api)
void REVFX_INIT(uint32_t platform, uint32_t api)
新:
__unit_callback int8_t unit_init(const unit_runtime_desc_t * desc)

サンプルで提供されているダミーのコードでは初期化関数の中で環境チェックを行っていますが、これはダミーのコードをそのまま使うのが良いです。
また、この初期化関数はSDK1と異なり、戻り値がありますので、最後に

    return k_unit_err_none;

しておく必要があります。

SDRAMの領域確保

主にディレイなどで使用する大容量バッファですが、SDK1ではSDRAM領域の確保は宣言的に行っていました。しかしSDK2では、これを初期化関数の中で手続き的に行う必要があります。特に、static宣言されているオブジェクトがインスタンス変数としてSDRAMを使用している場合、従来はmain()実行前にそのインスタンス変数は暗黙裡に初期化(SDRAM領域を確保)されていましたが、SDK2では初期化関数unit_init()の中で、明示的にインスタンス変数を初期化(SDRAM領域を確保して割り当て)する必要がある点に注意が必要です。確保したSDRAM領域はteardown()後に自動的に解放されるとソースコードのコメントには書かれています。

また、私が試した際には領域確保を連続16回までしか行えず、空き領域があるはずなのに17回目はエラー(NULLが返ってくる)になりました。私のコードが原因なのか、システム側でアロケーションテーブルのサイズが16しかないのか調査しきれていませんが、多くのバッファを必要とする場合には、合計のサイズで領域確保し、自分で分割して使用する方が良さそうです。

旧:
static __sdram float s_delay_ram_r[BUF_LEN];
新:
#include "utils/buffer_ops.h"
inline float *sdram_alloc_f32(size_t bufsize) {
    float *m = (float *)s_desc.hooks.sdram_alloc(bufsize * sizeof(float));
    if (m) {
        buf_clr_f32(m, bufsize);
    }
    return m;
}

{ //unit_init()内から以下を実行
    if (!desc->hooks.sdram_alloc)
        return k_unit_err_memory;
    s_delay_ram_r = sdram_alloc_f32(BUF_LEN);
}

パラメータ値の変更(FX_PARAM)

パラメータが変更された際に呼ばれる関数は、関数名だけが変わっています。新APIでは、エフェクトの種類に関わらず同一の関数名となっています。

旧:void MODFX_PARAM(uint8_t index, int32_t value)
新:__unit_callback void unit_set_param_value(uint8_t id, int32_t value)

しかし、それよりも注意しなければならないのはパラメータの型が変わっていることです。旧APIでは固定小数点数で渡されていたパラメータが、10ビットの整数値に変更になっています。解像度自体はいずれにせよ10ビットなのですが、旧APIは上位10ビットに意味があったのが、新APIでは下位10ビットに意味があるように変更されています。これに伴い、浮動小数点数への変換のための関数を変更する必要があります。

旧:const float valf = q31_to_f32(value);
新:const float valf = param_10bit_to_f32(value);

なお、q31_to_f32(q)は符号付き固定小数点数qをfloatにキャストしていましたが、param_10bit_to_f32(u)は符号なし整数uをunsigned shortにキャストしており、負の値は正しく変換できませんので注意が必要です。

信号処理(FX_PROCESS)

メインとなる信号処理の関数は、新APIでは、エフェクトの種類に関わらず同一の関数名となっています。ModFxは、Prologueのサブ・ティンバーに対応していましたが、新APIでは無くなりました。DelFxとRevFxでは、旧APIではデータのバッファを直接書き換える形態で、バッファが1つだけ渡されていましたが、新APIでは読み出しバッファと書き込みバッファが別となっています。コードもこれに合わせて修正する必要がありますが、通常はポインタの初期値の設定だけの変更で済むはずです。

旧:
void MODFX_PROCESS(const float *main_xn, float *main_yn,
                   const float *sub_xn,  float *sub_yn,
                   uint32_t frames)
void DELFX_PROCESS(float *main_xn, 
                   uint32_t frames)
void REVFX_PROCESS(float *main_xn, 
                   uint32_t frames)

新:
__unit_callback void unit_render(const float * in, float * out, uint32_t frames)

新規に追加されたコールバック関数

新APIでは実装しなければならないコールバック関数が増えています。しかし、移植にあたってはほとんどの関数は中身がカラッポでも動作はするはずです。ただし、空でも関数自体は定義しないと、ビルドは通ってもKONTROL Editorにロードしようとした時点でKONTROL Editorが落ちます。

__unit_callback void unit_teardown() {
}

__unit_callback void unit_reset() {
}

__unit_callback void unit_resume() {
}

__unit_callback void unit_suspend() {
}

__unit_callback int32_t unit_get_param_value(uint8_t id) {
    return パラメータの値;
}

__unit_callback const char * unit_get_param_str_value(uint8_t id, int32_t value) {
    return nullptr;
}

__unit_callback void unit_set_tempo(uint32_t tempo) {
}

__unit_callback void unit_tempo_4ppqn_tick(uint32_t counter) {
}

以上が移植作業の概要です。

私が実際に移植を行った際の差分が参考になるかもしれませんので、以下にGitHubのリンクを貼っておきます。

first commit of nts-1_mkii port · boochow/veryshort@147ee12
Contribute to boochow/veryshort development by creating an account on GitHub.
first commit of nts1mkii port · boochow/MasterVol2@7f2e6f7
master volume controller for logueSDK-ready synthesizers - first commit of nts1mkii port · boochow/MasterVol2@7f2e6f7
first commit of nts1mkii port · boochow/teleconf@20b8b4c
an effect which generates telephone-like sounds from inputs - first commit of nts1mkii port · boochow/teleconf@20b8b4c

コメント