ボイス・アサイナ (6)

ボイスを「再利用」する実際的なアルゴリズムは次のようになります。
「同音連打」に対しては「マルチ・アサイン」を仮定しています。 入力データの同時発音数が、ハードウェアの同時発音数を越える場合については考慮してありません。
ノートオン・メッセージに対して、

  • 「フリー・キュー」の先頭からボイスを1個取り出す
  • ボイスにノート番号に対応する音程データをセットし、アタック処理を開始する
  • ノートオン・メッセージの MIDI チャンネル番号から対応する「アクティブ・キュー」配列の位置を求める
  • ボイスをその「アクティブ・キュー」の最後尾に追加する

という処理を行います。
また、ノートオフ・メッセージに対しては、

  • ノートオフ・メッセージの MIDI チャンネル番号から「ホールド・キュー」、「フリー・キュー」配列の位置を求める
  • 「アクティブ・キュー」の先頭から順に要素をサーチしていき、(最初に見つかった) 同じノート番号のボイスを取り出す
  • Hold1 オフの場合
    • そのボイスにノートオフ処理 (リリース開始) を施す
    • そのボイスを「フリー・キュー」の最後尾に追加する
  • Hold1 オンの場合
    • そのボイスを「ホールド・キュー」の最後尾に追加する

処理を行います。
そして、cc#64 メッセージを受け取って Hold1 オフになった場合は、

  • 対応する MIDI チャンネル番号の「ホールド・キュー」内に保持されている全ボイスに対してノートオフ処理 (リリース開始) を施す
  • その「ホールド・キュー」の中身全部を「フリー・キュー」の最後尾に追加し、「ホールド・キュー」は空にする

処理を行います。
Hold1 オンになった場合には、特に処理は必要ありません。
前回示した双方向テイルキューによるデータ構造の模式図を下に再掲します。

ホールド・キューが「空」でないことから分かるように、

  • MIDI cc#64「Hold1」がオン、つまり、「ダンパー・ペダル」が踏まれている状態で、
  • しかも「F#」(ボイス 2)、「A#」(ボイス 1) の音は、すでに鍵盤を離した状態 (すでにノートオフ・メッセージが到着した) でホールド・キューに保持されており、
  • C#」(ボイス 5) は、まだ鍵盤が押された状態 (まだノートオフ・メッセージは来ていない) でアクティブ・キュー内に存在している

ことが分かります。
この状態を初期状態として、以降の説明を進めていきます。
スペースの節約と、図を作成する手間を省くために、"table" タグを使って「表組み」で簡略化した表現を使用します。「初期状態」に対応する表現は次のようになります。

ACTV 4 (C#)
HOLD 2 (F#) 1 (A#)
FREE  0   5   3 

この初期状態から、「D#」の音のノートオンが到着して、発音を始める場合を考えます。
まず、フリー・キューの先頭からボイスを 1 個 (ボイス番号 0) を取り出します。

ACTV 4 (C#)
HOLD 2 (F#) 1 (A#)
FREE  5   3 

 0 

具体的には、テイルキューから (先頭に限らず) 要素を取り出すには「TAILQ_REMOVE」マクロを使います。 先頭要素は「TAILQ_FIRST」で求められます。
取り出したボイスに「D#」の音の発音処理を施すと次のようになります。

 0 (D#) 

ACTV 4 (C#)
HOLD 2 (F#) 1 (A#)
FREE  5   3 

このボイスをアクティブ・キューの最後尾に追加します。 具体的には、「TAILQ_INSERT_TAIL」マクロを使います。

ACTV 4 (C#) 0 (D#)
HOLD 2 (F#) 1 (A#)
FREE  5   3 

これで新しいノートオンに対する処理は終了です。
次に、いま押されたばかりの「D#」の鍵盤が離され、ノートオフ・メッセージが到着したと仮定します。
アクティブ・キュー内をサーチし、見つかった「D#」を発音しているボイス番号 0 のボイスをキューから外すと次のようになります。

 0 (D#) 

ACTV 4 (C#)
HOLD 2 (F#) 1 (A#)
FREE  5   3 

テイルキュー内の要素を先頭から順に処理していくには、「TAILQ_FOREACH」または「TAILQ_FOREACH_SAFE」マクロを使います。
現在、Hold1 は「オン」ですから、外したボイスの行き先は「フリー・キュー」ではなく、「ホールド・キュー」の最後尾であり、処理の結果は次のようになります。

ACTV 4 (C#)
HOLD 2 (F#) 1 (A#) 0 (D#)
FREE  5   3 

次に、「ダンパー・ペダル」を離す、つまり「Hold1」が「オフ」になった場合を考えます。
まず、ホールド・キュー内に保持されているボイス全部にノートオフ処理を施します。

ACTV 4 (C#)
HOLD  2   1   0 
FREE  5   3 

ノートオフを施したということで、ボイスから音名を消してあります。
このホールド・キューの中身全体をフリー・キューの最後尾に、そのまま追加します。

ACTV 4 (C#)

HOLD

FREE  5   3   2   1   0 

"queue.h" には、あるテイルキュー全体を、別のテイルキューの後ろに連結して、ひとつのテイルキューにするためのマクロ「TAILQ_CONCAT」が用意されていて、この操作にびったりです。
この操作の結果、ホールド・キューは空になります。
最後に、(Hold1 がオフの状態で) 「C#」の鍵盤が離されてノートオフ・メッセージが到着すると、まず、「C#」の音を発音しているボイス 4 がアクティブ・キューから取り除かれ、ノートオフ処理が行われます。

 4 

ACTV

HOLD

FREE  5   3   2   1   0 

このボイスの行き先はフリー・キューの最後尾で、処理が終わると次のようになります。

ACTV

HOLD

FREE  5   3   2   1   0   4 

これでダンパー・ベダルも踏まれていない、鍵盤もすべて押されていない状態になりました。