NTS-1 mk2でのSDRAM利用上の注意点

logueシリーズ(drumlogue以外)では、バッファ専用のSDRAMを持っています。このSDRAMは主にディレイなどのエフェクトで利用するものです。NTS-1 mkIIではモジュレーションエフェクトが256KB、ディレイとリバーブは3MBまでの領域が利用可能です。ハードウェア的にはSDRAMは8MBが搭載されていますので、3つのエフェクトがそれぞれ最大限に使用しても大丈夫ということになります。

以前作成した2つのリバーブ(シュレーダーのリバーブオープンソースのFreeverb)はいずれもSDRAMを利用しています。これらをNTS-1 mkIIに移植してみましたので、この記事ではSDRAMの扱いについて気づいた点をメモしておきます。概要的なことは以下の記事にも追記しておきました。

NTS-1からNTS-1 mk2への移植ガイド(FX編)
これまでに作ってきた、NTS-1で動作するエフェクタ類のいくつかをNTS-1 mkIIに移植してみました。 自分の作成したものだけなので観測範囲は狭いのですが、移植の手順、ポイントみたいなものをメモしておきます。 ファイル構成 新旧を比較す...

なお、移植したコードはそれぞれのリポジトリの「nts1mkii」ブランチに、バイナリは他のプラットフォームのバイナリと一緒にReleasesに置いてあります。

領域確保を動的に行うことが必要

従来のlogue SDKでは、特定の静的変数をSDRAM領域に割り付ける指定を行うマクロが提供されていました。SDRAMはおそらくメモリ空間にマップされており、リンカに対して、静的変数を通常のメモリ空間ではなくSDRAM領域に置くように指定していたものと思われます。
SDK2.0ではこのマクロは無くなり、代わりにSDRAM領域を確保・解放する関数および残容量を確認する関数が提供されています。静的変数をSDRAM領域に置くことはできなくなりました。代わりに、コードの実行開始後に関数でSDRAM上の領域を確保します。

旧:
static __sdram float delay1_mem[DELAY1_LEN];

新:
static float *delay1_mem;

  (省略)
    if (!desc->hooks.sdram_alloc)
      return k_unit_err_memory;

    delay1_mem = (float *)desc->hooks.sdram_alloc(DELAY1_LEN * sizeof(float));
  (以下略)

ちなみにFreeverbでは、単純に上記の置き換えを行う以外に、以下のような実装になっている部分のロジックの修正が必要になりました。

・オブジェクトが静的に宣言されている
・そのオブジェクトの初期化コードがSDRAM領域上の変数参照している

C++のルールとして、staticなオブジェクトの初期化コードはユニットの関数が呼ばれる前に実行されます。しかし、この時点では、SDRAM上の領域は確保できていません。このような場合は、オブジェクトの初期化コードからSDRAM上の変数を参照しているコードを分離し、その部分はコード実行開始後に明示的に呼び出すように変更する必要があります。

エフェクト本体をシングルトンオブジェクトとして実装すると、上記のような状況は起こる可能性が高いと思われます。初期化コードからSDRAM上のバッファを触っているかどうかは注意すべきポイントでしょう。

SDRAM上に確保できる領域の個数制限

Freeverbの実装では、combフィルタを左右8個、allpassフィルタを左右4個ずつ、都合24個のバッファを必要とします。これを1つずつsdram_allocで確保していったところ、エラーとなってしまいました。

実験して調べてみたところ、現状、NTS-1 mk2ではSDRAM上に16個までの領域しか確保できないようです。このため、複数のバッファ用の領域をまとめて確保し、それを分割して使用するようにコードを変更しました。

16という数字は明示的に書かれていないので、将来的には変わる可能性もありますが、あまり大きな数字ではないので意識しておいたほうが良いかと思います。

領域の解放

サンプルのソースコードに以下のようなコメントがあり、sdram_allocで確保した領域はteardown後に自動的に解放されるということですので、エフェクトが終了する際のSDRAM領域の解放は、特に行う必要は無さそうです。

  inline void Teardown() {
    // Note: buffers allocated via sdram_alloc are automatically freed after unit teardown                                                                     
    // Note: cleanup and release resources if any                               
    allocated_buffer_ = nullptr;
  }

おそらくガベージコレクタなどは無いと思いますので、基本的にユニットの動作中に領域の確保と解放を繰り返すような使い方はしないほうが良いでしょう。unit_initの時点ですべてのメモリを確保し、あとはそれを使いまわすのが良いと思われます。なお、init中に領域確保に失敗した場合は、k_unit_err_memoryを返してunit_initを抜ける前に、それまでに確保した領域を解放する方が良いかもしれません。

コメント