Per-Note Expressionsサポートに関する覚書

調査の端緒

MIDIに変換するMMLで打ち込み作業をやっている時に、それぞれのノートの発音中に、それぞれのタイミングで音量を調整して打ち込みたいフレーズが出てきた。アルペジオ奏法みたいなことをシンセ音でやったことがあれば理解してもらえるかもしれない。たいていは単一のエンベロープで妥協しそうな気もするけど、特定のノートだけ違う力加減で発音したいような場合だ。

VelocityはMIDIノートオンのタイミングでしか指定できないから、VolumeかExpressionを使って実現するしかない(そのためのMML命令は実装してある)。しかしVolumeもExpressionもコントロールチェンジなのでチャンネルに帰属する。同じトラックで打ち込む以上は同じチャンネルで打ち込むことになる(理論的には別々のノートに別々のチャンネルを当てはめることは可能だけど、一般的なMML打ち込み作業でやるようなことではない)。

モダンなDAWとオーディオプラグインの世界では、MPE (MIDI Polyphonic Expression)とか、VSTのNote Expressionという、ノート毎にコントロールパラメーターを設定できる機能が存在している。MPEはMIDI 1.0で表現できる範囲内でこれを実現しているので、MPEをサポートする任意のMIDIバイスから、MPEをサポートする任意のDAWにデータを送ることが可能だし、その途中にどんなMIDI処理系が挟まっていても、メッセージを加工されない限りは大丈夫だ。AudioUnitでもAUAudioUnitsupportsMPEというプロパティが存在する。

そしてMIDI 2.0 UMPにはMPEの流れを汲むPer-Note ControllerやPer-Note Pitchbendといった機能が備わっている。これらを受信して処理できる「MIDIバイス」なら、これらのメッセージに対応して発音をノート別に調整できるはずだ。MPEで制御できるべきものとして規定されているのはピッチベンドとプレッシャー(アフター・タッチ)とCC 74(一般的にはカットオフ)くらいだが、MIDI 2.0 UMP仕様にそのような制限的な言及は無い(MPEでもその他をサポートするのは自由ではある。一部「推奨しない」ものもある。詳しくはここに書いた)。

VST3のNote Expressionは、MIDI 2.0 UMPにおけるPer-Note Controllerに近く、サポートされるノート別パラメーターについて具体的な制限は無く、プラグイン側が任意に規定し、ホスト側はそれをユーザーに提示するだけのようだ。

いずれにせよ、現時点でどこまで自作ツールで「実現」できる機能なのか把握しておきたかったので、関連仕様とサポートの実態を調べることにした…というのが半分で、残り半分くらいは日頃の調べ物を少しまとまったかたちで記録しておこうと思ってのことだ。

MPE入力の手法

ここでは話題の対象を既にDAWで実装例がいくつもあるMPEに限定するが、他のPer-Note Expressionについても当てはまる話ではある。

MPEは特定のノートに対してコントロールチェンジ等を送るもので、その入力は一般的なコントロールチェンジのようにトラック毎に入力させることはできない。ノート毎に入力させる必要がある。少なくとも、特定の音階に対して入力させる必要がある。常にあるノートでノートオンするたびに毎回同じコントロール値を期待するという状況は、ユースケースを考えてもあまり一般的ではなさそうだ。常に固定の相対ピッチで微分音作曲するなら、MTS (MIDI Tuning Standard) に則って設定したほうが適切で、オーディオプラグイン(インストゥルメント)の場合は微分音作曲に対応している場合はscalaファイルをサポートしているのが一般的だ。

自分が知っているところでは、Audio Developer Conference 2019でSteinbergの開発者がBitwig Studio、Cubase、ReaperでそれぞれどのようにMPEを入力するか紹介しているので、興味があれば参考にされたい。

youtu.be

この後ちょっと話に出てくるTracktion Waveformでもこんな編集画面がある:

MPE on Tracktion Waveform11

(ここでは何度も紹介しているけど、Tracktion Waveformは音楽編集・再生部分がtracktion_engineとしてGPLv3で公開されているので、次に言及する自作ツールでも活用している。)

TracktionのMPEサポート

自分のMML to Tracktionコンパイラaugene-ngでは、MMLコンパイラmugene-ngを使っていったんSMFに変換した上で、Tracktion Waveformで編集できる && Tracktion Engineで再生できるような楽曲データ(*.tracktionedit)に変換するものだった(過去形)。今回まとめている内容を実践する過程で、SMFからMIDI 2.0の楽曲データに移行している(mugene-ngでは既にMIDI 2.0楽曲データの生成をサポートしている)。そして、MIDI 2.0のPer-Note Pitchbendを、*.tracktionedit上ではMPEとして変換されるように実装した。

ただ前述の通り、MPEで標準的にサポートされているのは、ピッチベンドとアフター・タッチとCC #74(一般的にはカットオフ)のみであって、たとえばCC #07 (Volume) やCC #0B (Expression) に相当するものを、MIDI入力デバイスから送ることは出来ても、MPEのホスト側が受け付けないことはある。

今回は、VolumeやExpressionをPer-Note ExpressionとしてMMLで表現してUMPに変換して、それを*.tracktioneditに出力したいと考えた。しかし、Tracktionでは、MPEで送られてきた情報は、<NOTE>要素の子要素として、<PITCHBEND><PRESSURE><TIMBRE>のいずれかのみ保存されるようだ。つまり、Tracktion側にはNote Expressionを受け入れる場所がない。WaveformのGUI上にもこの3つの選択肢しか無いし、tracktion_engineの関連しそうなソースコードを見ても、この3種類しかサポートしていないようだ。

JUCEとNote Expressionサポート

そもそも、Steinbergのスタンスとしては、Note Expressionサポートはオーディオプラグインホスト側で実装されている必要がある。Tracktion EngineはJUCEで実装されているので、juce_audio_plugin_client(オーディオプラグインホスティング用モジュール)がVST3のNote Expressionをサポートしていなければ、Tracktion Engineが独自にサポートしている可能性は限りなく低い。そして実際JUCEではNote Expressionをサポートしていない。一方でJUCEはMPEのサポートには積極的だ。MPEは基本的にMIDI 1.0をサポートしていればあとは送り手と受け手の問題(中間層はただ転送するだけ)ということもあって、幅広く受け入れられている。

JUCEがNote Expressionをサポートしないのは、Note ExpressionがVST3固有の機能でしかない(AudioUnitでサポートされていない)からだと言えそうだ。もっとも最新のCoreAudioに含まれるAudioUnitではMIDI 2.0をMIDI入力として受け付けられるようなAPIを持っているように見える。もしそうだとしたら、juce::AudioProcessor::processBlock()MidiBufferがUMPを受け渡しできるように機能追加できるかもしれない。いずれにせよJUCE本体とTracktion Engineの両方に手を加えないと実現出来ないレベルの話だ。

VST3 MIDI Outputの問題

仮にJUCEに手を入れてMIDI 2.0をホストからサポートしたとしても、MIDI 2.0をサポートするMIDI出力プラグインの扱いが割と厄介な問題になる。主としてVST3がMIDI出力をまともにサポートしていないということが批判的に言及されることが多いようだ。入力をVST3のIMidiMappingでVST3のイベントに置換する時点でもイベント順序がノートとコントロールの間で保持されないなど致命的な問題があったようだ。このKVR Forumのスレッドの1ページ目の最後によくまとめられている。

www.kvraudio.com

JUCE ForumでもMIDI to MIDIプラグインまともに機能しないの?というスレッドがある。VST3のMIDI出力プラグインはbastardなどと言われている。もちろんjuce::AudioProcessorにproducesMidiというプロパティが在る程度には一般的な機能だった。

forum.juce.com

VST3のNote ExpressionとMPEどっちがどう良い?みたいな議論は割と昔からあるようだ。ここではBitwig StudioのKVR Forumの議論をひとつ紹介するにとどめておこう。

www.kvraudio.com

MIDI 2.0はNote Expression以上にサポートされていないが、Steinbergに手綱を握られること無く幅広く互換性が期待できるという意味では(そしてすでにAudioUnitでサポートされているいそうな様子からも)、今後はMIDI 2.0サポートを介在してのNote Expressionサポートが期待できそうではある。

LV2とMPE

VSTではNote Expression、AUではMIDI2の採用によってPer-Note Expressionが実現していそうだが現実的にはMPEしか出来ていないっぽい、ということはわかったが、LV2ではどうだろうか。実のところLV2ではMPEサポートも危うく、現実的にはLV2をサポートしているホストでMPEをサポートしているものは無いかもしれない。Ardourの場合は「MPEをサポートしようと思ったら根本的な再設計が必要だ」というのでサポートしていないようだ。

discourse.ardour.org

QTractorも「MPEはMIDI 1.0に基づいて入力できるでしょ」というレベルでサポートしていないようだ。

www.rncbc.org

この意味ではLV2を後からサポートするようになったReaperなどのほうが期待値が高いかもしれない(ReaperのLV2サポートの現状はちょっと検証できていない)。

MPEとサンプラー

話をホスト側からプラグイン側に移そう。今回自分が使いたかったのはFluidsynthやsfizzなどのサンプラーだ。Fluidsynthは良くも悪くもSF2ファイルをもとにMIDIソフトウェア・シンセサイザーとして動作することだけが考慮事項で、ここでは何度か紹介しているがMIDI 2.0を部分的にサポートしている。ただ、今回機能してほしいのはPer-Note Controllersで、これはまだ実装されていないようだ

sfizzのようなSFZサンプラーの場合、話はもう少しだけややこしくなる。というのは、SFZはもう少し細かいサンプラーの制御をSFZファイル側で記述する側面があるので、sfizzだけが独自に機能拡張できる話ではないからだ。開発者コミュニティにおける議論をざっくり検索して眺めてみた限りでは、SFZでもノート別にコントロール・チェンジの挙動を細かく記述することは可能なようだ。ただし、SFZのポピュラーな商用サンプラーSforzandoも含めてそれらをサポートしているものは見当たらないようだ。

github.com

LV2にMPEサポートが(仕様面にしろ実装面にしろ)無さそうなこともあって、sfizzなどのSFZサンプラーでMPEやNote Expressionがサポートされる日はまだまだ遠いかもしれない。

とはいえ、おそらくsfizzの開発者もこの辺のサポートが手薄であることを気にしているのではないかと自分は想像している。sfizzの主要な開発者の1人が今年公開していたのが、MPEをサポートするJUNO-60クローンだった(JUCEで作られている)。これは既存のシンセサイザーにMPEを組み込むのを試してみた側面があるように見える(READMEの冒頭で言及している)。

github.com

仕様上はPer-Note Expressionの実現を妨げるものがないとしたら、MIDI 2.0をサポートするプラグインホストが出てくれば、サンプラーがPer-Note Expressionをサポートする動きが出てくるかもしれない。

ちなみにnkiフォーマットはKontaktがそもそもVST3に対応していないレベルなので無理ではないかと思う(出来てMPEサポートくらい?)。