ボイス・アサイナ (5)

今回からは、リソースに制限のある現実の場合について考えます。
まず、2048 要素の 2 次元配列を用意するのは非現実的ですから、MIDI チャンネル番号か、ノート番号かの 1 次元配列に縮小することを考えます。
この場合、MIDI チャンネル番号をインデクスとする 1 次元配列にするのが得策です。
その理由として、まず単純に

  • ノート番号は 128 種あるが、MIDI チャンネルは 16 なので MIDI チャンネルの方が要素数が少なくてすむ

ことがあります。
また別の理由として、

  • ノート番号はボイス (発音エレメント) に設定するパラメタのひとつに過ぎず、他 MIDI チャンネルの同ノート番号との関係は薄いが、同一 MIDI チャンネルの音を発音する複数のボイス間では、音色パラメタ (プログラム・チェンジ) やコントロールの設定値を共有するので、関連性が高い

こともあげられます。
また、MIDI チャンネルによって「優先順位」を変える場合にも処理が容易になります。
さらに統合を進めて、全 MIDI チャンネル、全ノート番号のボイスをひとつのキューに保持する方法も考えられますが、そこまでいくと「やり過ぎ」で、効率の低下が生じてしまいます。
リソース無制限版では、ノートオフ・メッセージでボイスがリリース処理に入ると、キューから外して「使い捨て」にしていましたが、実際には「回収」して「再利用」しなければなりません。
そこで、ノートオフでアクティブ・キューから外したボイスは「フリー・キュー」の最後尾に追加して保持することにします。
「フリー・キュー」も FIFO 構成として、リリースに入った時刻が最も古いボイスがキューの先頭に位置するようにします。
ここで注意すべきことは、ボイスがアクティブ・キューからフリー・キューに移された時点では、リリースが始まったばかりで、まだ発音中であることです。
もちろん、リリースに入っていますから、「いずれは」音が減衰して「無音」になり、本当にボイスが「フリー」になります。
実際の音源プログラム中のボイス・アサイナではなく、ポリ音源を前提に作成された SMF をモノ音源用の SMF に変換する PC 上のプログラムでは、実際に発音される音のレベルをリアルタイムに知ることはできませんから、

  • リリースに入ってから時間が経過すればするほど、出力レベルが小さくなっている可能性が大きい

ことを仮定していることになります。
もちろん、音色パラメタによって、リリース時間はまちまちなので、リリース時間の長い音色のボイスが FIFO キューの先頭にあって、長い時間が経過しているのにもかかわらず、出力レベルがあまり減衰していないという状態も考えられます。
実際の音源プログラムでは、エンベロープ・ジェネレータをディジタル的に実現している場合には、出力レベルを正確に知ることができますから、フリー・キュー内のボイスをサーチして、最も出力レベルの小さなボイスを選び出すようなことも可能です。
このフリー・キューは、実際の音源プログラムの場合なら、MIDI チャンネルごとに設けることで効率化できる可能性がありますが、PC 上の SMF 変換プログラムでは、前述のように出力レベルを知ることはできないので、全 MIDI チャンネル分をまとめた 1 個だけを設けることにしました。
ボイス・アサイナ・プログラムでは、これらのアクテイブ/ホールド/フリー・キューに対する操作としては、

  • キュー内の要素を (順に) スキャンする
  • キュー内の任意の要素の取り出し
  • キュー先頭の要素の取り出し
  • キュー最後尾への要素の追加

の 4 種が必要になります。
単なる FIFO キューなら「リング・バッファ」で良いのですが、キュー内の任意の要素の取り出しが必要なので、「リスト構造」による方法で実現します。
自前で「リスト構造」を扱うプログラムを書く代わりに、BSD が発祥の "queue.h" で定義されているマクロを使って実現することにしました。
"queue.h" は gcc にも含まれていて、

  #include <sys/queue.h>

と宣言すれば使えるようになります。
gcc ベースではない、半導体メーカー製の C コンパイラには含まれていませんが、BSD ライセンスなので、公開するソースプログラム中に "queue.h" を含ませて頒布しても問題ありません。
"queue.h" 中には数種類のリスト構造を操作するマクロが含まれていますが、その中で、名前が「TAILQ_」で始まる「双方向リンク・テイル・キュー」を操作するためのマクロを利用することにしました。
この「テイルキュー」を使って、アクティブ / ホールド / フリー・キューを模式的に示すと下の図のようになります。

青色の正方形の箱がキュー内の要素を表しており、中に書かれた数字はボイス番号を表すものとします。
「F#」などと書いてあるのは発音している音程を示しており、「音程」であることを目立たせるために「シャープ」の付く音名を書いてあります。
キュー内の要素には、次の要素を指すポインタ「next」と、前の要素を指すポインタ「prev」が含まれています。 これが「双方向」あるいは「2重」と呼ばれる理由です。
キューの一番最後の要素の「next」ポインタは「斜線」となっていますが、これは Lisp 風に言えば「nil」、C 風に言えば「NULL」で、「何も指していない」ことを表しています。
青色の長方形の箱がキューの先頭 (と最後尾) を指すデータ構造で、"queue.h" では「head」と呼ばれています。
先頭だけでなく最後尾も指しているので「テイルキュー」と呼ばれています。
「first」と「last」のポインタのおかげで、「先頭からの要素の取り出し」と、「最後尾への要素の追加」が容易に行えるようになっています。
次回からは、もう少し具体的な説明に入ります。