オーディオ処理部分の基本形
オーディオプラグイン規格は、それぞれ互換性が無いものですが、楽器やエフェクターとしての基本的なオーディオ処理の部分には共通の部分が多いです。以下に擬似コードで列挙します。
create()
: プラグインを生成するactivate()
: プラグインを有効化するprocess(audio_buffers, midi_buffers)
: オーディオ入力とMIDI入力を受け取って出力を生成するdeactivate()
: プラグインを無効化するdestroy()
: プラグインを破棄する
「MIDI入力」と書いている部分は、実際にはMIDIではなくプラグイン規格ごとに異なりますが、これについては次の節でじっくり説明します。ここではオーディオ入力について掘り下げます。
オーディオデータとバス、ポート、チャンネル
「オーディオデータ」に渡ってくるのは、「全オーディオチャンネル」分のfloatあるいはdoubleの配列です。最近はdoubleを使う状況もありますが、一般的にはfloatが使われるでしょう。「全オーディオチャンネル」とは、1つ以上の「オーディオバス」ごとに割り当てられた「チャンネル数」です。オーディオバスは少しややこしい概念で、難しければ飛ばしても何とかなる概念です(飛ばした場合は「ステレオ」になると考えれば良いです)。もう少しちゃんと説明すると、オーディオバスとはチャンネルの構成に名前と特性がいくつか付いた構造です。具体的なものを挙げたほうがわかりやすいでしょう:
- mono
- stereo
- 5.1ch
- 7.1ch
- ambisonic
これらの名前は暗黙的に「メイン入出力」を含意していることが多いです。これらはそれぞれ固有のチャンネルを有しており、一般的にはチャンネルにも名前が付いています("L", "R", "front left", "rear back right", ...)
「メイン」があるということは補助的なものもある…というわけで、オーディオプラグインには「サイドチェイン」として直接再生するわけではなく波形の計算に補助的に利用するオーディオデータを渡すこともできます。LV2ではCVPortがサイドチェインを実現するためのものです(CVにはLV2のどんなポートを使っても特に問題はなく、あくまで指針としてCVPortを使うのが適切ということです)。
ホストDAWは、各トラックについて、オーディオプラグインに「利用できるオーディオバスの情報」を提示してもらい、トラックで利用できるバスを調整し、バスのオーディオバッファを準備します。メインのバスは一般的には1つになりますが、補助的にサイドチェインのバスが複数有効化される可能性があります。そして実際にオーディオ処理process()
を呼び出すとき、有効なオーディオバス全てに関連付けられたオーディオチャンネル分のバッファが、引数として渡されることになります。
VSTの場合はバスと呼ばれますが、CLAPではポートと呼ばれています。LV2でもポートと呼ばれるのですが、これはバスに相当する概念ではなくチャンネルに相当する概念なので(つまり「ステレオ」ポートにはならず「左」ポートと「右」ポートになる)、混乱しないように区別して捉えておかなければなりません。
オーディオプラグインにおけるMIDIサポートの基本
CLAP開発チームがさかんに宣伝している機能のひとつが高度なノート命令ですが、これを理解するためには、そもそもオーディオプラグインはどうやってMIDI命令を処理しているのか、理解しておく必要があるでしょう。
実のところ、オーディオプラグインでなまのMIDIメッセージをそのまま処理することはあまりありません。オーディオプラグインフォーマットごとにMIDIより高度な(データ幅や追加情報が多い)ノートイベントやコントロールチェンジイベントなどが規定されていて、MIDI入力はDAWによって変換されてプラグインに渡されるのが一般的です。プラグインを開発できるSDKによっては、受け取ったイベントメッセージをMIDIメッセージに(DAWとは逆の方向に)変換して、VSTやAUやLV2の共通コードとして実装できるようにすることも多いです(JUCEなど)。
オーディオプラグインがMIDIのようなイベントを受け取る入口は主に2つあります:
- イベントとして受信する: DAWでPCに接続されたMIDIキーボードの鍵盤を押すと今のトラックのオーディオプラグイン設定で非リアルタイムに演奏されます(発音します)
- 演奏命令として受け取る: DAWに打ち込んだ内容を再生するとき、DAWは対象の全トラックでリアルタイムオーディオスレッドを用いて「オーディオループ処理」を回します。1回の処理はリアルタイムと言える間隔(10ミリ秒程度)の間に全て完了しなければなりません。そうしないと不自然な「空き」が生じてしまい、遅延やノイズの原因になります。このループの中に、演奏命令としてイベント列も含まれることになります。
ここで一つ気をつけないといけないのは、オーディオループはリアルタイムスレッドで回っていて、一方でMIDIキーボード等の入力はI/Oを伴う非リアルタイムスレッドから受け取るということです。リアルタイムでオーディオ処理を回しているときに、ちょっとだけ時間を借りてMIDI入力に対応する音声を生成して戻ってくる…ということはできません。
実際にDAWを使っているときは、トラックを再生しながら同時にMIDIキーボードを叩いていることがあり、この意味では受信した入力イベントは演奏命令にマージされると考えて良いでしょう。ただしそのタイミングは1回のオーディオループで処理される実際の演奏時間の長さによって変わります。もし仮に1回のオーディオループで1000ミリ秒分のオーディオデータが処理されるとしたら、オーディオループは1秒に1回しか回りません。その間のどのタイミングでMIDIキーボードの鍵盤が押されたとしても、ノートイベントが発生するのはその1秒単位のスライスの始点でのみということになります。
DSPコードの共通化
VSTもAUもCLAPもそれぞれバラバラなパラメーターとイベントの機構をもっていますが、VSTでもAUでもLV2など他のフォーマットでもプラグインをリリースしたいと思ったら、それぞれのフォーマットに固有のDSP処理を書くよりも、MIDIのような共通の音楽演奏命令のデータを使って記述するようにしたほうが、再利用性が高いです。
DSPでコードを共通化できるということになったら、JUCEのようなクロスプラットフォーム・マルチプラグインフォーマット用フレームワークのほか、FAUSTやSOUL(SOULの権利はROLI社に残ってしまったので創始者のjulesは今は新しくc-majorという言語を作っているようですが)といったオーディオ処理に特化した言語、あるいはMATLABのような言語環境も適用できる可能性が高くなります。
例として、オーディオプラグインフォーマットのようなものが登場すると、MDAという定番DSPモジュールの集合体がmda-vst, mda-vst3, mda-lv2といった感じで移植されますが、このオリジナルのMDAのコードは汎用的なDSPコードとして書かれています。各プラグインフォーマットのプロジェクトは、それぞれのプラグインのAPIを使った「ラッパー」となっているわけです。
この領域で新しく開発されているのが、MIDI 2.0サポートの追加です。共通コードでMIDI 1.0の表現力しか得られないのは残念な状態だったわけですが、MIDI 2.0が利用できるようになれば、note expressionや32ビットパラメーター(CCやNRPN = Assignable Controller)、アーティキュレーションなどを処理できるようになります。Appleがいち早くAudioUnitで実装しており、JUCEが追従しています2022.7.19追記: これは正しくない理解でした。AUでは任意の可変長バッファではなくMusicDeviceMIDIEvent()
でMIDIイベントをサポートしており、32ビットintの3値ではUMPをサポートする余地がありません。JUCEのソースでも現状対応する実装がありません。。CLAPも規格のレベルでMIDI 2.0メッセージをそのまま(VST3とは異なり、そのまま)処理できるようになっています。
各プラグインフォーマットにおけるイベント定義
VST2の時代は、MIDI入力はそのままのかたちでプラグインが受け取って処理できるようになっていました。一方でプログラムチェンジとパラメーターも利用できるようになっていたので、ある意味役割がかぶっていた状態でした。
Steinbergはこれを問題だと考えて、VST3ではMIDI入力をパラメーターとしてプラグイン側でマッピングして処理させることにしました。その結果、VST3ではなまのMIDIメッセージを受け取ることができなくなりました。これはそれなりに大きな副作用があり、まずVST3プラグインでどんなDAWでも一意に復元できるようなMIDI出力が出せなくなりました(全てVSTイベントとして出力されるため)。そしてJUCEのようにVST3イベントをMIDIメッセージに変換したうえでオーディオ処理に渡す仕組みがあって、かつプログラム番号もMIDIのプログラムチェンジとならずにそのまま渡される仕組みになっていると、DAWにはMIDIのプログラムチェンジを入力したはずなのに、プラグイン側にはプログラムチェンジとして渡されない…といった問題もありました。JUCEのように「プラグイン側ではホストからのイベントは全てMIDIメッセージに変換してその範囲で処理する」仕組みになっていると、この影響をストレートに受けることになります。
CLAPは、VST3とは異なり、MIDI 1.0イベント、MIDI 2.0イベント、CLAPイベントの3種類がサポートされており、DAWはどの入力もそのままプラグインに渡せば良いということになります。
VST3は「役割が重複したら困るだろうからホストが全部われわれのVSTイベントに変換するのでそれを使え」という姿勢ですが、CLAPの場合は「複数の入力イベントで役割が重複するかもしれないが、その解決は自分でやれ」ということになります。
ノートエクスプレッション
ノートエクスプレッション (Note Expression) またはノート別エクスプレッション (Per-Note Expression) とは、プラグインに送信された全てのノートではなく、特定のノートだけに適用されるプラグインのパラメーターを実現する仕組みです。MIDI 2.0 UMPには含まれている命令ですが、MIDI 1.0の原規格には含まれておらず、MPE (MIDI Polyphonic Expression) という派生規格で別途実現しています。もしMPEが無ければ、一般的にMIDI 2.0がサポートされていないOSではMIDI入力デバイスから受け取ることができないでしょう。
(ノートエクスプレッションについては過去に一度踏み込んだ話を書いたことがあるので、ここで改めて踏み込むのは避けます。)