DAWでトラックとプラグインの数が増えてくると、リアルタイム処理が間に合わなくなる可能性が十分にある。オーディオ処理が追いつかなくなるのは多数のプラグインをリアルタイムに連続的に処理しなければならないことが原因だ。編集作業で手を加えていないトラックの再生結果は変わらないはずなので、毎回オーディオグラフを辿って全てのノードを処理しなくてもいいはずだ。
この問題を解決というか回避するのがトラックのフリーズ機能だ。フリーズしたトラックの内容はエフェクトプラグインなしのオーディオトラックと同等なので、多数のトラックがあっても概ね問題ない。
フリーズ機能の基本要件
フリーズ機能の原理的な要件はシンプルだが、設計にはいくつか選択肢がある:
- フリーズは「いつ」行われるべきか:
- 明示的なトラックのフリーズ作業を指示されたときに実行する
- 編集作業が行われたら直ちに実行する
- 編集作業が行われなくなってきたら自動的に実行する
- 再生処理中にもバックグラウンドで実行する: これは意外と難しい。具体的には、再生処理中にはフリーズ処理のためのレンダリングを同じプラグイン インスタンスで行うことはできない。複数の再生位置でオーディオ処理を行う必要があるが、パラメーターや状態の値は1つのインスタンスで同時に1つしか指定できないし、全パラメーターを再生処理とフリーズ処理で切り替えながら行うのは不可能だ(理論的に不可能というわけではないが技術的に無意味だ)。そのため、ここでは複数のプラグイン インスタンスが必要になるが、巨大な音源を伴うサンプラーのインスタンスなども複製しなければならないとなると、メモリの圧迫にも繋がる。
- フリーズはどの単位で実行すべきか。トラック単位で行うのは確実だ。クリップ単位で行うほうが効率的かもしれないが、この場合、クリップの移動先によっては他のクリップとの重なり合いや末尾処理の影響が他のクリップのフリーズ状態に及び、解除の連鎖を実装しなければならなくなるだろう。移動によって重なり合いが解除されたクリップも、改めてフリーズする必要が生じる。
- フリーズは「どこで」スイッチを提供すべきか。フリーズの最小単位はオーディオプラグインを含むグラフの影響範囲であり、トラック単位でオン・オフを判断するが、スイッチもトラック単位で制御できるようにすべきか。できたほうが機能的には望ましいが、ユーザーにとっては手間が増える側面もある。逆に全トラックのフリーズを自動化することがユーザーの希望に沿っているか。再生に支障が生じる場面は無いか。
フリーズ機能実装の前提機能
フリーズ機能を実現するためには、いくつかの機能が前提として存在している必要がある:
- トラック単位のオフラインレンダリング: これが必須なのは言うまでもないだろう。もしオフラインレンダリングにかけてみて、期待通りの音が出ない場合は、オフラインレンダリングの実装を修正するところから始めないといけない。実装によっては、たとえばオフラインレンダリングの時だけ(あるいはプログラマブルに「ロード即再生開始」のようなプログラムで演奏した時だけ)プリセットのロードが間に合わないうちにレンダリングを開始してしまうような問題が発生することがある。
- トラックに対する編集作業を無効化あるいは検出できる仕組みが備わっている必要がある。これにはプロジェクトモデルに「保存されていない変更」を追跡できる仕組みが備わっていれば、それを流用できるが、もし無ければゼロから作るしかなく、これは割と大掛かりな仕組みの整備が必要になる。オーディオグラフのゲインを1つ変えるだけでも「変更」になる(これがフリーズ解除を引き起こすかどうかは、フリーズされたトラックにどこまでの加工を許すかという設計の問題になる)。
- バックグラウンドで別のプラグイン インスタンスを生成して処理する仕組みを用意しているのでない限り、演奏が「停止してから」フリーズ処理を開始する必要がある。演奏の「停止」の判断にはtail lengthを取得してその完了まではオーディオ処理を続行し、理論上も無音になるのを待って開始することが望ましい。各プラグインフォーマットではtail lengthを提供するAPIが用意されている。
- フリーズをクリップ単位で実装する場合、前出の通り、クリップの重なり合いを検出する仕組みが必要だ。歌唱合成などのボーカルエディターでは一般的な機能といえるが、DAWでは必須の機能というわけではない。
楽曲のプロジェクトモデル、あるいはドキュメントモデルと呼ばれるものは、単純なプラグインホストやシーケンサーエンジン(それだけでも既に大変なのだが)を作っているだけでは必要が無いが、DAWでもundo/redoなどの編集機能を実装したり、ARAをサポートしたり、VST3のインターフェース関数IComponentHandler2::setDirty()をサポートするに至る開発を行っていると必要になってくる(この関数自体はプラグイン側が状態を管理する前提で作られたAPIで、ホスト側のsingle source of truthを否定する存在なので、あまり行儀が良いとは言えないが)。
実装
uapmdの実装においては、FrozenTrackManagerというクラスが実装の大部分を担っているが、トラックのフリーズはTailProcessManagerというクラスに実装されたaddTransportQuietListener()で登録するイベント(再生がtailも含めて無音になると発火するイベント)がトリガーになって実行される。再生処理のイベントでそのまま登録すると、tail処理が未だに行われていてもフリーズが始まってしまうので、完全に沈黙してから開始するという流れだ。