Slide 1

Slide 1 text

オーディオプラグイン勉強会#1 2022.7.6

Slide 2

Slide 2 text

この勉強会の目的 オーディオプラグインフォーマットの仕様・機能を勉強することで、 - プラグイン開発やホスト開発のレベルを高められるようになる - オーディオプラグインの使い方を詳しく知ることができる

Slide 3

Slide 3 text

今回のお題 CLAP 1.0仕様が登場したので、これを契機にプラグイン規格の機能を学ぶ ※「CLAP出羽〜」みたいな話はしますが各種規格の話をしましょう …!

Slide 4

Slide 4 text

オーディオプラグイン規格の意義 複数の{ホスト=DAW, プラグイン}で使える ※独自プラグインは特定DAW専用: FL native plugins, Reaper jsfx, Avid AAX... 本質的には[音声とコントロール]の[入力から出力]を生成できれば何でもよい

Slide 5

Slide 5 text

CLAPには何の意味がある? CLAPはVSTやAUやLV2の代わりに利用できるオーディオプラグイン規格 Win/Mac/Linuxで使える 商用利用の制限がごく小さいMITライセンス コミュニティで仕様が議論・決定できる(?)  ※現状Bitwig社員しかいなそう (Bitwig専用ならDAW専用のプラグインフォーマットとあまり変わらない…)

Slide 6

Slide 6 text

CLAP 1.0 6月にバージョン1.0としてu-heとBitwigが大々的に宣伝 (KVR / HN) この時点では商用製品はu-he製プラグインのみ、DAWはBitwig Studioのみ OSSプラグインはsurge synthesizerとclap-juce-extensionsを中心にいくつか 「仕様書」が不在 - Cヘッダファイルのコメントがほぼ全て  開発チームは「仕様は明確でわかりやすい」と主張 利用できる環境が著しく限定されるという意味では実用性は未来形 開発コミュニティは比較的活発

Slide 7

Slide 7 text

clapdb

Slide 8

Slide 8 text

CLAPの開発環境 配布形態: 共有ライブラリ(拡張子 *.clap → dll/dylib/soに移行?) 言語: C, C++, Rust? (clap-sys) plain SDK: clap, clap-helpers 各種ライブラリで: clap-juce-extensions, iPlug2 (clap branch), Avendish, ... IDE等はvs/vscode/xcode/CLionなど何でも(一般的なプラグイン開発と同様)

Slide 9

Slide 9 text

CLAPプラグイン開発のアプローチ 1) JUCE, iPlug2など既存のプラグイン開発ライブラリを使う →モジュール追加等だけで既存コードを使えそう 2) DSPは汎用的に作って、CLAP APIに自前で結びつける(多分割と簡単) clap-helpersを使うのもよし(ただそんなに多機能ではない) 3) 最初からCLAP用に作る…?(メリットがない? / 完全にCLAPに乗っかるならこれ) clap-juce-extensionsではCLAPのAPIをjuce::AudioProcessorから直接呼び出せる (surgeがこれを利用している)

Slide 10

Slide 10 text

汎用的なDSPコード? (CLAPなら)init() → [ activate() → process() → deactivate() ] → destroy() の流れ process() でオーディオ・イベント入力を受け取って同出力を生成  - オーディオバッファ(float*,double*,void* x channels, per ports,buses,buffers)  - タイムスタンプ付きイベントストリーム(MIDIイベントなど) MATLAB, FAUST, SOULなどDSP開発に向いた言語もある(どれもCLAPには未対応)

Slide 11

Slide 11 text

DAWでCLAPプラグインをロードする仕組み 【プラグインの探索】 CLAP_PATH以下でプラグイン (*.clapファイル)を検索 (環境変数 or 既定パス) factoryからリスト取得可能→ 【プラグインのロード】 auto plugin = dlopen("path/to/myplugin.clap", ...); auto entry = (clap_plugin_entry_t*) dlsym(plugin, "clap_entry"); entry->init("/path/to/myplugin.clap"); auto factory = entry->get_factory( CLAP_PLUGIN_FACTORY_ID); clap_plugin_descriptor_t *desc = factory->get_plugin_descriptor(factory, index); const clap_plugin_t *plugin = factory->create_plugin(factory, host, "pluginID"); // hostは後で説明

Slide 12

Slide 12 text

プラグインのリスト プラグインからメタデータを取得する (製品名、ベンダー、カテゴリ etc.) allowlist/denylistの管理にも使われる VST3, AU - プラグインの生成が必要  → 重い LV2 - テキストで記述  → 軽い / コードと不一致の可能性 CLAP - プラグインDLLのロードが必要  →プラグイン生成しないだけ軽い

Slide 13

Slide 13 text

DAWの操作とプラグインAPI (説明していない機能もいくつか) ● プラグインをトラックに追加  create → activate → process ● プラグインをトラックから削除 (deactivate)? → destroy ● 有効・無効切り替え      activate/deactivate -or- process bypassed ● 再生             process (with sorted events) ● MIDIノートオン・オフ     send event -or- process ● プラグインUIの表示・非表示  show GUI, hide GUI ● パラメーターの変更      set parameter(s), (save state)? ● プログラム/プリセットの選択 load preset, (save preset/state)? → 追加機能は拡張 (extensions)として仕様で規定される

Slide 14

Slide 14 text

オーディオプラグインの拡張機能(そもそも論) プラグイン規格の最重要課題は相互運用性の確保 = APIを破壊的に変更しない でも技術仕様に追加機能の要求は必ず発生する  オーディオバス(サラウンド/3Dオーディオ対応)、MPE、MIDI 2.0、... 一般的な機能でもプラグイン機能本体から切り離して追加定義することもある  パラメーター定義/操作、GUI、状態(state)の読み書き、プリセット、... 追加定義の破壊的変更はダメージが少ない VST3 MA (module architecture), LV2 extensions, CLAP extensions, ...

Slide 15

Slide 15 text

CLAPの拡張機能

Slide 16

Slide 16 text

CLAPプラグインが拡張機能を実装する例 clap_plugin_state_t state_ext{ my_plugin_state_save, my_plugin_state_load}; void* my_plugin_get_extension( const clap_plugin_t* plugin, const char* id) { if (!strcmp(id, CLAP_EXT_STATE)) return &state_ext; ... return nullptr; } bool my_plugin_state_save( const clap_plugin_t *plugin, const clap_ostream_t *stream) { ... // save to `stream` return true; } bool my_plugin_state_load( const clap_plugin_t *plugin, const clap_ostream_t *stream) { ... // load from `stream` return true; }

Slide 17

Slide 17 text

process (1) オーディオバッファの処理 VST3 ProcessData, AU AudioBufferList, LV2 void**, CLAP clap_process_t  CLAPのオーディオ関連拡張: audio-ports-config, ambisonic, surround, cv  LV2だけ事前にポインタをconnect_port()で用意しておく仕組み  ※設計としては失敗… LV2: the good, bad, and ugly  floatだけでなくdoubleを使えることもある 一般にステレオ以外は事前にDAWとの間でオーディオ バス/ポートの調整が必要 mainポート(mono/stereo/5.1/7.1/surround)とサイドチェイン / CVの違いも ⚠DRAFT ⚠DRAFT ⚠DRAFT

Slide 18

Slide 18 text

process (2) MIDIイベントのサポート方式 VST2: MIDIメッセージをプラグインが直接受け取って処理可能 VST3: ホストがVSTイベントに変換したものを受け取る LV2: MIDIメッセージはAtom_Sequenceに格納されて渡される(直接処理可能) CLAP: MIDI 1/2, CLAPイベントの3種類に標準で対応 = 直接処理可能  ※プラグインは「対応する種類」のイベントは全て実装しなければならない

Slide 19

Slide 19 text

DAWはMIDI入力イベントをどうやってプラグインに渡す? プラグインによっては、MIDIから独自イベントに変換する(VST3) コントロールチェンジはmidi-mappingによって変換されうる(DAW, CLAP拡張) アクティブな状態と非アクティブな状態で流れが違う ● process()がリアルタイムで回っている時: オーディオ処理はタイトに回っているの で、非リアルタイム処理を差し込む余地はない → 入力イベントはタイムスタンプを付けてキューに溜め込んでおく → process()が呼び出されたときに楽曲中のイベントとマージする ● process()が回っていない時: そのままプラグインにイベントを送る

Slide 20

Slide 20 text

パラメーターのサポート 一般的にはfloat型(32bit)で0.0-1.0 / MIDI 2.0のCC等はuint32_t相当の整数 パラメーターの取得・設定方法  VST, CLAP, AU: イベント  LV2: パラメーター毎のポート or 1つのイベントポートにLV2 Patchを流す  ※前者はパラメーターが数百件になりうる現代では非現実的  LV2 PatchはMIDI 2.0 Property Exchangeと似ている("GET", "SET" etc.)

Slide 21

Slide 21 text

GUIのサポート 設計がむずかしい - 一般的にはクロスプラットフォームGUIが前提 ※AudioUnit... - 完全なGUIフレームワークのAPIを規定するのは無謀 - 開発ライブラリやSDKによってGUIの設計は変わる(言語/ランタイムも) →プラグインが用意したGUIをホストに統合できる仕組みだけ用意する どうやって? - VST3, LV2: プラグインがViewを返し、ホストのWindowにアタッチ - CLAP: ホストが親Windowをset_parent()でプラグインに渡す - Win32 (HWND) / Cocoa (NSView) / X11 (Window) の個別APIが定義される

Slide 22

Slide 22 text

GUIのサポート GUIサポートAPIの具体的な内容 - show/hide, get size / resize (is-fixed?), get/set scale factor, set window title ... - request show/hide, notify resize, notify closed, ... - APIによってはウィンドウの移動通知なども UIとロジックの分離 - LV2はdll / dylib / soのレベルで分離(自分でDSPを参照することはまあ可能) - CLAPはそこまで分離しない

Slide 23

Slide 23 text

地味な拡張機能 log - ログを記録する  ※ ホストで全プラグインのログを統一的に出力できる  ※ リアルタイムスレッドからは直接書き出せない state - ユーザーが調整したパラメーター状態をホストで保存・復元する  ※ 内容(semantics)がわかるようにする? しない? (yes: LV2, no: CLAP) presets - パラメーター等のベンダー定義/ユーザー定義のプリセット  ※ stateと機能的には重複する部分が多い  ※ *.vstpresetファイル

Slide 24

Slide 24 text

CLAPの一般的な拡張機能 tail length ノートオフした後、完全に消音するまでの時間をホストが取得できる report latency プラグインにどれくらい遅延が生じるのかホストが取得できる

Slide 25

Slide 25 text

CLAPの「拡張として有るのは珍しい」標準的な機能 tunings  微分音(microtonal)を実現するための拡張  MTS (MIDI Tuning Standards) を受信できるプラグインなら拡張機能は不要  プラグインも独自にscala/kbmサポートを実装でき、ホストとのやり取りは不要  でもCLAPでCLAP eventsをサポートするならこれを使うしか無い check-for-update  プラグインの更新版があれば教える(たぶん珍しい…VST/AUにある?) render オフライン(非リアルタイム)レンダリングモードを設定できる ⚠DRAFT ⚠DRAFT

Slide 26

Slide 26 text

CLAPのユニークな機能 パラメーターモジュレーションの仕様 ("non-destructive automation") ホストへのノート終了通知? (clap_event_note_end) host managed voices (voice-info draft) ホストによる thread_pool という名前の並列処理サポート

Slide 27

Slide 27 text

CLAPのパラメーター関連イベント CLAP_EVENT_NOTE_EXPRESSION : per-note expression(対象はenum) CLAP_EVENT_PARAM_VALUE: パラメーターを設定(ノート別も可能) CLAP_EVENT_PARAM_MOD: モジュレーション オートメーションが終わったらパラメーターを元に戻せる ("non-destructive") ※ how? CLAP_EVENT_PARAM_GESTURE_BEGIN, ~_END: ジェスチャー操作 {開始/終了} ジェスチャー: begin〜endの間はユーザーがツマミを回しているので、それを前提に オートメーションの記録や再生を行える  ※プラグインが?

Slide 28

Slide 28 text

ノートオン、ノートオフ、その他!? CLAPのノート命令はいろいろ追加機能が付いている Host >>> note on >>> Plugin Host >>> note off >>> Plugin Host <<< note end <<< Plugin (リリース処理完了) NOTE_CHOKE: ノートオフに伴うリリースを無視して遮音するためのもの:  MIDIのAll notes offに相当する機能の実現  closed hi-hatとopen hi-hatなど相互に排他的な楽器を演奏する命令  ※DAWでの入力をどうやって実装するのかは不透明

Slide 29

Slide 29 text

voice-info: 発音数管理 プラグインから同時発音数・最大発音数を 取得できる ※CLAPは「ホストからボイスを管理したい」 という指向が強い。Bitwig-ism? ⚠DRAFT typedef struct clap_voice_info { uint32_t voice_count; uint32_t voice_capacity; uint64_t flags; } clap_voice_info_t; enum { CLAP_VOICE_INFO_SUPPORTS_OVER LAPPING_NOTES = 1 << 0, }; // It is useful for the host when performing polyphonic modulations, // because the host needs its own voice management and should try to follow // what the plugin is doing: // - make the host's voice pool coherent with what the plugin has // - turn the host's voice management to mono when the plugin is mono

Slide 30

Slide 30 text

thread_pool CLAPのthread_poolはスレッドプールではない(断言)  マルチコアCPUを活用した処理の並列化が目的   タスクもリアルタイムで完了することが前提 synth voiceの計算など  単一プラグインの並列処理最適化をホストで実装する仕様はかしこい  (トラックごとに1つのCPUコアでも十分という気もしなくもない …) LV2のWorkerはスレッドプール  タスク(のハンドル)をプラグインとホストでやり取りするAPI  ファイルI/Oなどで使う  CLAPにはこの機能が無い(プラグインでスレッドプールを作れなくはない)

Slide 31

Slide 31 text

プラグインフォーマットの設計に関する若干の私見 プラグインAPIで何より重要なのは破壊的変更が無いこと その次にAPIに曖昧さ(解釈の幅)がないことが重要 プラグインAPIがクリーンであることの価値は高いが必須ではない  ※開発用ライブラリで吸収できる 高度な機能は「必須でない」「使わなくても処理結果が変わらない」ことが重要 特定の特定のDAWに特化した機能は汎用規格では規定しない  ※代替性があることがユーザーにとっては重要